-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support arbitrary Number implementation for Object and Number deseria…
…lization (#1290) * Object and Number type adapters number deserialization can be configured * Change wording of ToNumberStrategy documentation * Use inline links in doc sparingly If the element has already been linked before, don't create a link for every subsequent occurrence. See also (slightly dated) https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#inlinelinks * Link to default to-number policies in ToNumberStrategy doc * Reduce code duplication for deserializing Number * Hide default factory constants of NumberTypeAdapter and ObjectTypeAdapter This encapsulates the logic a little bit better. Additionally refactored factory created by NumberTypeAdapter to only create TypeAdapter once and then have factory reuse that adapter for better performance. Co-authored-by: Marcono1234 <[email protected]>
- Loading branch information
1 parent
1cc1627
commit fe30b85
Showing
10 changed files
with
575 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
* Copyright (C) 2021 Google Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.gson; | ||
|
||
import java.io.IOException; | ||
import java.math.BigDecimal; | ||
|
||
import com.google.gson.internal.LazilyParsedNumber; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.MalformedJsonException; | ||
|
||
/** | ||
* An enumeration that defines two standard number reading strategies and a couple of | ||
* strategies to overcome some historical Gson limitations while deserializing numbers as | ||
* {@link Object} and {@link Number}. | ||
* | ||
* @see ToNumberStrategy | ||
*/ | ||
public enum ToNumberPolicy implements ToNumberStrategy { | ||
|
||
/** | ||
* Using this policy will ensure that numbers will be read as {@link Double} values. | ||
* This is the default strategy used during deserialization of numbers as {@link Object}. | ||
*/ | ||
DOUBLE { | ||
@Override public Double readNumber(JsonReader in) throws IOException { | ||
return in.nextDouble(); | ||
} | ||
}, | ||
|
||
/** | ||
* Using this policy will ensure that numbers will be read as a lazily parsed number backed | ||
* by a string. This is the default strategy used during deserialization of numbers as | ||
* {@link Number}. | ||
*/ | ||
LAZILY_PARSED_NUMBER { | ||
@Override public Number readNumber(JsonReader in) throws IOException { | ||
return new LazilyParsedNumber(in.nextString()); | ||
} | ||
}, | ||
|
||
/** | ||
* Using this policy will ensure that numbers will be read as {@link Long} or {@link Double} | ||
* values depending on how JSON numbers are represented: {@code Long} if the JSON number can | ||
* be parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a | ||
* {@code Double} value. If the parsed double-precision number results in a positive or negative | ||
* infinity ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the | ||
* {@code JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException} | ||
* is thrown. | ||
*/ | ||
LONG_OR_DOUBLE { | ||
@Override public Number readNumber(JsonReader in) throws IOException, JsonParseException { | ||
String value = in.nextString(); | ||
try { | ||
return Long.parseLong(value); | ||
} catch (NumberFormatException longE) { | ||
try { | ||
Double d = Double.valueOf(value); | ||
if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { | ||
throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + in); | ||
} | ||
return d; | ||
} catch (NumberFormatException doubleE) { | ||
throw new JsonParseException("Cannot parse " + value, doubleE); | ||
} | ||
} | ||
} | ||
}, | ||
|
||
/** | ||
* Using this policy will ensure that numbers will be read as numbers of arbitrary length | ||
* using {@link BigDecimal}. | ||
*/ | ||
BIG_DECIMAL { | ||
@Override public BigDecimal readNumber(JsonReader in) throws IOException { | ||
String value = in.nextString(); | ||
try { | ||
return new BigDecimal(value); | ||
} catch (NumberFormatException e) { | ||
throw new JsonParseException("Cannot parse " + value, e); | ||
} | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Copyright (C) 2021 Google Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.gson; | ||
|
||
import java.io.IOException; | ||
|
||
import com.google.gson.stream.JsonReader; | ||
|
||
/** | ||
* A strategy that is used to control how numbers should be deserialized for {@link Object} and {@link Number} | ||
* when a concrete type of the deserialized number is unknown in advance. By default, Gson uses the following | ||
* deserialization strategies: | ||
* | ||
* <ul> | ||
* <li>{@link Double} values are returned for JSON numbers if the deserialization type is declared as | ||
* {@code Object}, see {@link ToNumberPolicy#DOUBLE};</li> | ||
* <li>Lazily parsed number values are returned if the deserialization type is declared as {@code Number}, | ||
* see {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.</li> | ||
* </ul> | ||
* | ||
* <p>For historical reasons, Gson does not support deserialization of arbitrary-length numbers for | ||
* {@code Object} and {@code Number} by default, potentially causing precision loss. However, | ||
* <a href="https://tools.ietf.org/html/rfc8259#section-6">RFC 8259</a> permits this: | ||
* | ||
* <pre> | ||
* This specification allows implementations to set limits on the range | ||
* and precision of numbers accepted. Since software that implements | ||
* IEEE 754 binary64 (double precision) numbers [IEEE754] is generally | ||
* available and widely used, good interoperability can be achieved by | ||
* implementations that expect no more precision or range than these | ||
* provide, in the sense that implementations will approximate JSON | ||
* numbers within the expected precision. A JSON number such as 1E400 | ||
* or 3.141592653589793238462643383279 may indicate potential | ||
* interoperability problems, since it suggests that the software that | ||
* created it expects receiving software to have greater capabilities | ||
* for numeric magnitude and precision than is widely available. | ||
* </pre> | ||
* | ||
* <p>To overcome the precision loss, use for example {@link ToNumberPolicy#LONG_OR_DOUBLE} or | ||
* {@link ToNumberPolicy#BIG_DECIMAL}.</p> | ||
* | ||
* @see ToNumberPolicy | ||
* @see GsonBuilder#setObjectToNumberStrategy(ToNumberStrategy) | ||
* @see GsonBuilder#setNumberToNumberStrategy(ToNumberStrategy) | ||
*/ | ||
public interface ToNumberStrategy { | ||
|
||
/** | ||
* Reads a number from the given JSON reader. A strategy is supposed to read a single value from the | ||
* reader, and the read value is guaranteed never to be {@code null}. | ||
* | ||
* @param in JSON reader to read a number from | ||
* @return number read from the JSON reader. | ||
* @throws IOException | ||
*/ | ||
public Number readNumber(JsonReader in) throws IOException; | ||
} |
82 changes: 82 additions & 0 deletions
82
gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* Copyright (C) 2020 Google Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.gson.internal.bind; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.gson.JsonSyntaxException; | ||
import com.google.gson.ToNumberStrategy; | ||
import com.google.gson.ToNumberPolicy; | ||
import com.google.gson.TypeAdapter; | ||
import com.google.gson.TypeAdapterFactory; | ||
import com.google.gson.reflect.TypeToken; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.JsonToken; | ||
import com.google.gson.stream.JsonWriter; | ||
|
||
import java.io.IOException; | ||
|
||
/** | ||
* Type adapter for {@link Number}. | ||
*/ | ||
public final class NumberTypeAdapter extends TypeAdapter<Number> { | ||
/** | ||
* Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}. | ||
*/ | ||
private static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY = newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER); | ||
|
||
private final ToNumberStrategy toNumberStrategy; | ||
|
||
private NumberTypeAdapter(ToNumberStrategy toNumberStrategy) { | ||
this.toNumberStrategy = toNumberStrategy; | ||
} | ||
|
||
private static TypeAdapterFactory newFactory(ToNumberStrategy toNumberStrategy) { | ||
final NumberTypeAdapter adapter = new NumberTypeAdapter(toNumberStrategy); | ||
return new TypeAdapterFactory() { | ||
@SuppressWarnings("unchecked") | ||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { | ||
return type.getRawType() == Number.class ? (TypeAdapter<T>) adapter : null; | ||
} | ||
}; | ||
} | ||
|
||
public static TypeAdapterFactory getFactory(ToNumberStrategy toNumberStrategy) { | ||
if (toNumberStrategy == ToNumberPolicy.LAZILY_PARSED_NUMBER) { | ||
return LAZILY_PARSED_NUMBER_FACTORY; | ||
} else { | ||
return newFactory(toNumberStrategy); | ||
} | ||
} | ||
|
||
@Override public Number read(JsonReader in) throws IOException { | ||
JsonToken jsonToken = in.peek(); | ||
switch (jsonToken) { | ||
case NULL: | ||
in.nextNull(); | ||
return null; | ||
case NUMBER: | ||
case STRING: | ||
return toNumberStrategy.readNumber(in); | ||
default: | ||
throw new JsonSyntaxException("Expecting number, got: " + jsonToken); | ||
} | ||
} | ||
|
||
@Override public void write(JsonWriter out, Number value) throws IOException { | ||
out.value(value); | ||
} | ||
} |
Oops, something went wrong.