Formats codebase (#2531)

* Formats `.java` files

* Formats `.md` files
This commit is contained in:
Maicol 2023-11-06 20:59:01 +01:00 committed by GitHub
parent cb6643f148
commit 2c94c757a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
229 changed files with 11195 additions and 9016 deletions

View File

@ -15,28 +15,29 @@
*/ */
package com.google.gson.extras.examples.rawcollections; package com.google.gson.extras.examples.rawcollections;
import java.util.ArrayList;
import java.util.Collection;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import java.util.ArrayList;
import java.util.Collection;
public class RawCollectionsExample { public class RawCollectionsExample {
static class Event { static class Event {
private String name; private String name;
private String source; private String source;
private Event(String name, String source) { private Event(String name, String source) {
this.name = name; this.name = name;
this.source = source; this.source = source;
} }
@Override @Override
public String toString() { public String toString() {
return String.format("(name=%s, source=%s)", name, source); return String.format("(name=%s, source=%s)", name, source);
} }
} }
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({"unchecked", "rawtypes"})
public static void main(String[] args) { public static void main(String[] args) {
Gson gson = new Gson(); Gson gson = new Gson();
Collection collection = new ArrayList(); Collection collection = new ArrayList();

View File

@ -38,9 +38,7 @@ import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
import java.util.Queue; import java.util.Queue;
/** /** Writes a graph of objects as a list of named nodes. */
* Writes a graph of objects as a list of named nodes.
*/
// TODO: proper documentation // TODO: proper documentation
public final class GraphAdapterBuilder { public final class GraphAdapterBuilder {
private final Map<Type, InstanceCreator<?>> instanceCreators; private final Map<Type, InstanceCreator<?>> instanceCreators;
@ -48,11 +46,15 @@ public final class GraphAdapterBuilder {
public GraphAdapterBuilder() { public GraphAdapterBuilder() {
this.instanceCreators = new HashMap<>(); this.instanceCreators = new HashMap<>();
this.constructorConstructor = new ConstructorConstructor(instanceCreators, true, Collections.<ReflectionAccessFilter>emptyList()); this.constructorConstructor =
new ConstructorConstructor(
instanceCreators, true, Collections.<ReflectionAccessFilter>emptyList());
} }
public GraphAdapterBuilder addType(Type type) { public GraphAdapterBuilder addType(Type type) {
final ObjectConstructor<?> objectConstructor = constructorConstructor.get(TypeToken.get(type)); final ObjectConstructor<?> objectConstructor = constructorConstructor.get(TypeToken.get(type));
InstanceCreator<Object> instanceCreator = new InstanceCreator<Object>() { InstanceCreator<Object> instanceCreator =
new InstanceCreator<Object>() {
@Override @Override
public Object createInstance(Type type) { public Object createInstance(Type type) {
return objectConstructor.construct(); return objectConstructor.construct();
@ -79,6 +81,7 @@ public final class GraphAdapterBuilder {
static class Factory implements TypeAdapterFactory, InstanceCreator<Object> { static class Factory implements TypeAdapterFactory, InstanceCreator<Object> {
private final Map<Type, InstanceCreator<?>> instanceCreators; private final Map<Type, InstanceCreator<?>> instanceCreators;
@SuppressWarnings("ThreadLocalUsage") @SuppressWarnings("ThreadLocalUsage")
private final ThreadLocal<Graph> graphThreadLocal = new ThreadLocal<>(); private final ThreadLocal<Graph> graphThreadLocal = new ThreadLocal<>();
@ -95,7 +98,8 @@ public final class GraphAdapterBuilder {
final TypeAdapter<T> typeAdapter = gson.getDelegateAdapter(this, type); final TypeAdapter<T> typeAdapter = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class); final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() { return new TypeAdapter<T>() {
@Override public void write(JsonWriter out, T value) throws IOException { @Override
public void write(JsonWriter out, T value) throws IOException {
if (value == null) { if (value == null) {
out.nullValue(); out.nullValue();
return; return;
@ -144,7 +148,8 @@ public final class GraphAdapterBuilder {
} }
} }
@Override public T read(JsonReader in) throws IOException { @Override
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -207,13 +212,12 @@ public final class GraphAdapterBuilder {
} }
/** /**
* Hook for the graph adapter to get a reference to a deserialized value * Hook for the graph adapter to get a reference to a deserialized value before that value is
* before that value is fully populated. This is useful to deserialize * fully populated. This is useful to deserialize values that directly or indirectly reference
* values that directly or indirectly reference themselves: we can hand * themselves: we can hand out an instance before read() returns.
* out an instance before read() returns.
* *
* <p>Gson should only ever call this method when we're expecting it to; * <p>Gson should only ever call this method when we're expecting it to; that is only when we've
* that is only when we've called back into Gson to deserialize a tree. * called back into Gson to deserialize a tree.
*/ */
@Override @Override
public Object createInstance(Type type) { public Object createInstance(Type type) {
@ -231,22 +235,17 @@ public final class GraphAdapterBuilder {
static class Graph { static class Graph {
/** /**
* The graph elements. On serialization keys are objects (using an identity * The graph elements. On serialization keys are objects (using an identity hash map) and on
* hash map) and on deserialization keys are the string names (using a * deserialization keys are the string names (using a standard hash map).
* standard hash map).
*/ */
private final Map<Object, Element<?>> map; private final Map<Object, Element<?>> map;
/** /** The queue of elements to write during serialization. Unused during deserialization. */
* The queue of elements to write during serialization. Unused during
* deserialization.
*/
private final Queue<Element<?>> queue = new ArrayDeque<>(); private final Queue<Element<?>> queue = new ArrayDeque<>();
/** /**
* The instance currently being deserialized. Used as a backdoor between * The instance currently being deserialized. Used as a backdoor between the graph traversal
* the graph traversal (which needs to know instances) and instance creators * (which needs to know instances) and instance creators which create them.
* which create them.
*/ */
private Element<Object> nextCreate; private Element<Object> nextCreate;
@ -254,37 +253,24 @@ public final class GraphAdapterBuilder {
this.map = map; this.map = map;
} }
/** /** Returns a unique name for an element to be inserted into the graph. */
* Returns a unique name for an element to be inserted into the graph.
*/
public String nextName() { public String nextName() {
return "0x" + Integer.toHexString(map.size() + 1); return "0x" + Integer.toHexString(map.size() + 1);
} }
} }
/** /** An element of the graph during serialization or deserialization. */
* An element of the graph during serialization or deserialization.
*/
static class Element<T> { static class Element<T> {
/** /** This element's name in the top level graph object. */
* This element's name in the top level graph object.
*/
private final String id; private final String id;
/** /** The value if known. During deserialization this is lazily populated. */
* The value if known. During deserialization this is lazily populated.
*/
private T value; private T value;
/** /** This element's type adapter if known. During deserialization this is lazily populated. */
* This element's type adapter if known. During deserialization this is
* lazily populated.
*/
private TypeAdapter<T> typeAdapter; private TypeAdapter<T> typeAdapter;
/** /** The element to deserialize. Unused in serialization. */
* The element to deserialize. Unused in serialization.
*/
private final JsonElement element; private final JsonElement element;
Element(T value, String id, TypeAdapter<T> typeAdapter, JsonElement element) { Element(T value, String id, TypeAdapter<T> typeAdapter, JsonElement element) {

View File

@ -21,13 +21,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Use this annotation to indicate various interceptors for class instances after * Use this annotation to indicate various interceptors for class instances after they have been
* they have been processed by Gson. For example, you can use it to validate an instance * processed by Gson. For example, you can use it to validate an instance after it has been
* after it has been deserialized from Json. * deserialized from Json. Here is an example of how this annotation is used:
* Here is an example of how this annotation is used: *
* <p>Here is an example of how this annotation is used: * <p>Here is an example of how this annotation is used:
*
* <pre> * <pre>
* &#64;Intercept(postDeserialize=UserValidator.class) * &#64;Intercept(postDeserialize=UserValidator.class)
* public class User { * public class User {
@ -56,8 +56,8 @@ import java.lang.annotation.Target;
public @interface Intercept { public @interface Intercept {
/** /**
* Specify the class that provides the methods that should be invoked after an instance * Specify the class that provides the methods that should be invoked after an instance has been
* has been deserialized. * deserialized.
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public Class<? extends JsonPostDeserializer> postDeserialize(); public Class<? extends JsonPostDeserializer> postDeserialize();

View File

@ -24,11 +24,10 @@ import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
/** /** A type adapter factory that implements {@code @Intercept}. */
* A type adapter factory that implements {@code @Intercept}.
*/
public final class InterceptorFactory implements TypeAdapterFactory { public final class InterceptorFactory implements TypeAdapterFactory {
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Intercept intercept = type.getRawType().getAnnotation(Intercept.class); Intercept intercept = type.getRawType().getAnnotation(Intercept.class);
if (intercept == null) { if (intercept == null) {
return null; return null;
@ -52,11 +51,13 @@ public final class InterceptorFactory implements TypeAdapterFactory {
} }
} }
@Override public void write(JsonWriter out, T value) throws IOException { @Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value); delegate.write(out, value);
} }
@Override public T read(JsonReader in) throws IOException { @Override
public T read(JsonReader in) throws IOException {
T result = delegate.read(in); T result = delegate.read(in);
postDeserializer.postDeserialize(result); postDeserializer.postDeserialize(result);
return result; return result;

View File

@ -18,16 +18,14 @@ package com.google.gson.interceptors;
import com.google.gson.InstanceCreator; import com.google.gson.InstanceCreator;
/** /**
* This interface is implemented by a class that wishes to inspect or modify an object * This interface is implemented by a class that wishes to inspect or modify an object after it has
* after it has been deserialized. You must define a no-args constructor or register an * been deserialized. You must define a no-args constructor or register an {@link InstanceCreator}
* {@link InstanceCreator} for such a class. * for such a class.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
*/ */
public interface JsonPostDeserializer<T> { public interface JsonPostDeserializer<T> {
/** /** This method is called by Gson after the object has been deserialized from Json. */
* This method is called by Gson after the object has been deserialized from Json.
*/
public void postDeserialize(T object); public void postDeserialize(T object);
} }

View File

@ -16,24 +16,24 @@
package com.google.gson.typeadapters; package com.google.gson.typeadapters;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.PostConstruct;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory; import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.PostConstruct;
public class PostConstructAdapterFactory implements TypeAdapterFactory { public class PostConstructAdapterFactory implements TypeAdapterFactory {
// copied from https://gist.github.com/swankjesse/20df26adaf639ed7fd160f145a0b661a // copied from https://gist.github.com/swankjesse/20df26adaf639ed7fd160f145a0b661a
@Override @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
for (Class<?> t = type.getRawType(); (t != Object.class) && (t.getSuperclass() != null); t = t.getSuperclass()) { for (Class<?> t = type.getRawType();
(t != Object.class) && (t.getSuperclass() != null);
t = t.getSuperclass()) {
for (Method m : t.getDeclaredMethods()) { for (Method m : t.getDeclaredMethods()) {
if (m.isAnnotationPresent(PostConstruct.class)) { if (m.isAnnotationPresent(PostConstruct.class)) {
m.setAccessible(true); m.setAccessible(true);
@ -45,7 +45,7 @@ public class PostConstructAdapterFactory implements TypeAdapterFactory {
return null; return null;
} }
final static class PostConstructAdapter<T> extends TypeAdapter<T> { static final class PostConstructAdapter<T> extends TypeAdapter<T> {
private final TypeAdapter<T> delegate; private final TypeAdapter<T> delegate;
private final Method method; private final Method method;
@ -54,7 +54,8 @@ public class PostConstructAdapterFactory implements TypeAdapterFactory {
this.method = method; this.method = method;
} }
@Override public T read(JsonReader in) throws IOException { @Override
public T read(JsonReader in) throws IOException {
T result = delegate.read(in); T result = delegate.read(in);
if (result != null) { if (result != null) {
try { try {
@ -69,7 +70,8 @@ public class PostConstructAdapterFactory implements TypeAdapterFactory {
return result; return result;
} }
@Override public void write(JsonWriter out, T value) throws IOException { @Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value); delegate.write(out, value);
} }
} }

View File

@ -32,10 +32,11 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
/** /**
* Adapts values whose runtime type may differ from their declaration type. This * Adapts values whose runtime type may differ from their declaration type. This is necessary when a
* is necessary when a field's type is not the same type that GSON should create * field's type is not the same type that GSON should create when deserializing that field. For
* when deserializing that field. For example, consider these types: * example, consider these types:
* <pre> {@code *
* <pre>{@code
* abstract class Shape { * abstract class Shape {
* int x; * int x;
* int y; * int y;
@ -56,8 +57,11 @@ import java.util.Map;
* Shape topShape; * Shape topShape;
* } * }
* }</pre> * }</pre>
* <p>Without additional type information, the serialized JSON is ambiguous. Is *
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code * <p>Without additional type information, the serialized JSON is ambiguous. Is the bottom shape in
* this drawing a rectangle or a diamond?
*
* <pre>{@code
* { * {
* "bottomShape": { * "bottomShape": {
* "width": 10, * "width": 10,
@ -70,10 +74,13 @@ import java.util.Map;
* "x": 4, * "x": 4,
* "y": 1 * "y": 1
* } * }
* }}</pre> * }
* This class addresses this problem by adding type information to the * }</pre>
* serialized JSON and honoring that type information when the JSON is *
* deserialized: <pre> {@code * This class addresses this problem by adding type information to the serialized JSON and honoring
* that type information when the JSON is deserialized:
*
* <pre>{@code
* { * {
* "bottomShape": { * "bottomShape": {
* "type": "Diamond", * "type": "Diamond",
@ -88,32 +95,44 @@ import java.util.Map;
* "x": 4, * "x": 4,
* "y": 1 * "y": 1
* } * }
* }}</pre> * }
* Both the type field name ({@code "type"}) and the type labels ({@code * }</pre>
* "Rectangle"}) are configurable. *
* Both the type field name ({@code "type"}) and the type labels ({@code "Rectangle"}) are
* configurable.
* *
* <h2>Registering Types</h2> * <h2>Registering Types</h2>
* Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field *
* name to the {@link #of} factory method. If you don't supply an explicit type * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field name to the
* field name, {@code "type"} will be used. <pre> {@code * {@link #of} factory method. If you don't supply an explicit type field name, {@code "type"} will
* be used.
*
* <pre>{@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
* = RuntimeTypeAdapterFactory.of(Shape.class, "type"); * = RuntimeTypeAdapterFactory.of(Shape.class, "type");
* }</pre> * }</pre>
* Next register all of your subtypes. Every subtype must be explicitly *
* registered. This protects your application from injection attacks. If you * Next register all of your subtypes. Every subtype must be explicitly registered. This protects
* don't supply an explicit type label, the type's simple name will be used. * your application from injection attacks. If you don't supply an explicit type label, the type's
* <pre> {@code * simple name will be used.
*
* <pre>{@code
* shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle"); * shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
* shapeAdapterFactory.registerSubtype(Circle.class, "Circle"); * shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
* shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond"); * shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
* }</pre> * }</pre>
*
* Finally, register the type adapter factory in your application's GSON builder: * Finally, register the type adapter factory in your application's GSON builder:
* <pre> {@code *
* <pre>{@code
* Gson gson = new GsonBuilder() * Gson gson = new GsonBuilder()
* .registerTypeAdapterFactory(shapeAdapterFactory) * .registerTypeAdapterFactory(shapeAdapterFactory)
* .create(); * .create();
* }</pre> * }</pre>
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code *
* Like {@code GsonBuilder}, this API supports chaining:
*
* <pre>{@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class) * RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
* .registerSubtype(Rectangle.class) * .registerSubtype(Rectangle.class)
* .registerSubtype(Circle.class) * .registerSubtype(Circle.class)
@ -121,14 +140,18 @@ import java.util.Map;
* }</pre> * }</pre>
* *
* <h2>Serialization and deserialization</h2> * <h2>Serialization and deserialization</h2>
* In order to serialize and deserialize a polymorphic object, *
* you must specify the base type explicitly. * In order to serialize and deserialize a polymorphic object, you must specify the base type
* <pre> {@code * explicitly.
*
* <pre>{@code
* Diamond diamond = new Diamond(); * Diamond diamond = new Diamond();
* String json = gson.toJson(diamond, Shape.class); * String json = gson.toJson(diamond, Shape.class);
* }</pre> * }</pre>
*
* And then: * And then:
* <pre> {@code *
* <pre>{@code
* Shape shape = gson.fromJson(json, Shape.class); * Shape shape = gson.fromJson(json, Shape.class);
* }</pre> * }</pre>
*/ */
@ -140,8 +163,7 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final boolean maintainType; private final boolean maintainType;
private boolean recognizeSubtypes; private boolean recognizeSubtypes;
private RuntimeTypeAdapterFactory( private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) {
Class<?> baseType, String typeFieldName, boolean maintainType) {
if (typeFieldName == null || baseType == null) { if (typeFieldName == null || baseType == null) {
throw new NullPointerException(); throw new NullPointerException();
} }
@ -151,34 +173,35 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
} }
/** /**
* Creates a new runtime type adapter using for {@code baseType} using {@code * Creates a new runtime type adapter using for {@code baseType} using {@code typeFieldName} as
* typeFieldName} as the type field name. Type field names are case sensitive. * the type field name. Type field names are case sensitive.
* *
* @param maintainType true if the type field should be included in deserialized objects * @param maintainType true if the type field should be included in deserialized objects
*/ */
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) { public static <T> RuntimeTypeAdapterFactory<T> of(
Class<T> baseType, String typeFieldName, boolean maintainType) {
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
} }
/** /**
* Creates a new runtime type adapter using for {@code baseType} using {@code * Creates a new runtime type adapter using for {@code baseType} using {@code typeFieldName} as
* typeFieldName} as the type field name. Type field names are case sensitive. * the type field name. Type field names are case sensitive.
*/ */
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) { public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false);
} }
/** /**
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as the type field
* the type field name. * name.
*/ */
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) { public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
return new RuntimeTypeAdapterFactory<>(baseType, "type", false); return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
} }
/** /**
* Ensures that this factory will handle not just the given {@code baseType}, but any subtype * Ensures that this factory will handle not just the given {@code baseType}, but any subtype of
* of that type. * that type.
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public RuntimeTypeAdapterFactory<T> recognizeSubtypes() { public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
@ -187,11 +210,10 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
} }
/** /**
* Registers {@code type} identified by {@code label}. Labels are case * Registers {@code type} identified by {@code label}. Labels are case sensitive.
* sensitive.
* *
* @throws IllegalArgumentException if either {@code type} or {@code label} * @throws IllegalArgumentException if either {@code type} or {@code label} have already been
* have already been registered on this type adapter. * registered on this type adapter.
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) { public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
@ -207,11 +229,11 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
} }
/** /**
* Registers {@code type} identified by its {@link Class#getSimpleName simple * Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are
* name}. Labels are case sensitive. * case sensitive.
* *
* @throws IllegalArgumentException if either {@code type} or its simple name * @throws IllegalArgumentException if either {@code type} or its simple name have already been
* have already been registered on this type adapter. * registered on this type adapter.
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) { public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
@ -240,7 +262,8 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
} }
return new TypeAdapter<R>() { return new TypeAdapter<R>() {
@Override public R read(JsonReader in) throws IOException { @Override
public R read(JsonReader in) throws IOException {
JsonElement jsonElement = jsonElementAdapter.read(in); JsonElement jsonElement = jsonElementAdapter.read(in);
JsonElement labelJsonElement; JsonElement labelJsonElement;
if (maintainType) { if (maintainType) {
@ -250,27 +273,35 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
} }
if (labelJsonElement == null) { if (labelJsonElement == null) {
throw new JsonParseException("cannot deserialize " + baseType throw new JsonParseException(
+ " because it does not define a field named " + typeFieldName); "cannot deserialize "
+ baseType
+ " because it does not define a field named "
+ typeFieldName);
} }
String label = labelJsonElement.getAsString(); String label = labelJsonElement.getAsString();
@SuppressWarnings("unchecked") // registration requires that subtype extends T @SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label); TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) { if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + " subtype named " throw new JsonParseException(
+ label + "; did you forget to register a subtype?"); "cannot deserialize "
+ baseType
+ " subtype named "
+ label
+ "; did you forget to register a subtype?");
} }
return delegate.fromJsonTree(jsonElement); return delegate.fromJsonTree(jsonElement);
} }
@Override public void write(JsonWriter out, R value) throws IOException { @Override
public void write(JsonWriter out, R value) throws IOException {
Class<?> srcType = value.getClass(); Class<?> srcType = value.getClass();
String label = subtypeToLabel.get(srcType); String label = subtypeToLabel.get(srcType);
@SuppressWarnings("unchecked") // registration requires that subtype extends T @SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType); TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
if (delegate == null) { if (delegate == null) {
throw new JsonParseException("cannot serialize " + srcType.getName() throw new JsonParseException(
+ "; did you forget to register a subtype?"); "cannot serialize " + srcType.getName() + "; did you forget to register a subtype?");
} }
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
@ -282,8 +313,11 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
JsonObject clone = new JsonObject(); JsonObject clone = new JsonObject();
if (jsonObject.has(typeFieldName)) { if (jsonObject.has(typeFieldName)) {
throw new JsonParseException("cannot serialize " + srcType.getName() throw new JsonParseException(
+ " because it already defines a field named " + typeFieldName); "cannot serialize "
+ srcType.getName()
+ " because it already defines a field named "
+ typeFieldName);
} }
clone.add(typeFieldName, new JsonPrimitive(label)); clone.add(typeFieldName, new JsonPrimitive(label));

View File

@ -16,6 +16,10 @@
package com.google.gson.typeadapters; package com.google.gson.typeadapters;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException; import java.text.ParseException;
import java.text.ParsePosition; import java.text.ParsePosition;
@ -24,11 +28,6 @@ import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public final class UtcDateTypeAdapter extends TypeAdapter<Date> { public final class UtcDateTypeAdapter extends TypeAdapter<Date> {
private final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC"); private final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
@ -113,6 +112,7 @@ public final class UtcDateTypeAdapter extends TypeAdapter<Date> {
return formatted.toString(); return formatted.toString();
} }
/** /**
* Zero pad a number to a specified length * Zero pad a number to a specified length
* *
@ -160,7 +160,8 @@ public final class UtcDateTypeAdapter extends TypeAdapter<Date> {
int hour = 0; int hour = 0;
int minutes = 0; int minutes = 0;
int seconds = 0; int seconds = 0;
int milliseconds = 0; // always use 0 otherwise returned date will include millis of current time // always use 0 otherwise returned date will include millis of current time
int milliseconds = 0;
if (checkOffset(date, offset, 'T')) { if (checkOffset(date, offset, 'T')) {
// extract hours, minutes, seconds and milliseconds // extract hours, minutes, seconds and milliseconds
@ -230,7 +231,8 @@ public final class UtcDateTypeAdapter extends TypeAdapter<Date> {
fail = e; fail = e;
} }
String input = (date == null) ? null : ("'" + date + "'"); String input = (date == null) ? null : ("'" + date + "'");
throw new ParseException("Failed to parse date [" + input + "]: " + fail.getMessage(), pos.getIndex()); throw new ParseException(
"Failed to parse date [" + input + "]: " + fail.getMessage(), pos.getIndex());
} }
/** /**
@ -254,7 +256,8 @@ public final class UtcDateTypeAdapter extends TypeAdapter<Date> {
* @return the int * @return the int
* @throws NumberFormatException if the value is not a number * @throws NumberFormatException if the value is not a number
*/ */
private static int parseInt(String value, int beginIndex, int endIndex) throws NumberFormatException { private static int parseInt(String value, int beginIndex, int endIndex)
throws NumberFormatException {
if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) { if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) {
throw new NumberFormatException(value); throw new NumberFormatException(value);
} }

View File

@ -19,17 +19,15 @@ package com.google.gson.graph;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.junit.Test; import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public final class GraphAdapterBuilderTest { public final class GraphAdapterBuilderTest {
@Test @Test
public void testSerialization() { public void testSerialization() {
@ -41,27 +39,25 @@ public final class GraphAdapterBuilderTest {
paper.beats = rock; paper.beats = rock;
GsonBuilder gsonBuilder = new GsonBuilder(); GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder() new GraphAdapterBuilder().addType(Roshambo.class).registerOn(gsonBuilder);
.addType(Roshambo.class)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create(); Gson gson = gsonBuilder.create();
assertEquals("{'0x1':{'name':'ROCK','beats':'0x2'}," + assertEquals(
"'0x2':{'name':'SCISSORS','beats':'0x3'}," + "{'0x1':{'name':'ROCK','beats':'0x2'},"
"'0x3':{'name':'PAPER','beats':'0x1'}}", + "'0x2':{'name':'SCISSORS','beats':'0x3'},"
+ "'0x3':{'name':'PAPER','beats':'0x1'}}",
gson.toJson(rock).replace('"', '\'')); gson.toJson(rock).replace('"', '\''));
} }
@Test @Test
public void testDeserialization() { public void testDeserialization() {
String json = "{'0x1':{'name':'ROCK','beats':'0x2'}," + String json =
"'0x2':{'name':'SCISSORS','beats':'0x3'}," + "{'0x1':{'name':'ROCK','beats':'0x2'},"
"'0x3':{'name':'PAPER','beats':'0x1'}}"; + "'0x2':{'name':'SCISSORS','beats':'0x3'},"
+ "'0x3':{'name':'PAPER','beats':'0x1'}}";
GsonBuilder gsonBuilder = new GsonBuilder(); GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder() new GraphAdapterBuilder().addType(Roshambo.class).registerOn(gsonBuilder);
.addType(Roshambo.class)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create(); Gson gson = gsonBuilder.create();
Roshambo rock = gson.fromJson(json, Roshambo.class); Roshambo rock = gson.fromJson(json, Roshambo.class);
@ -78,9 +74,7 @@ public final class GraphAdapterBuilderTest {
String json = "{'0x1':{'name':'SUICIDE','beats':'0x1'}}"; String json = "{'0x1':{'name':'SUICIDE','beats':'0x1'}}";
GsonBuilder gsonBuilder = new GsonBuilder(); GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder() new GraphAdapterBuilder().addType(Roshambo.class).registerOn(gsonBuilder);
.addType(Roshambo.class)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create(); Gson gson = gsonBuilder.create();
Roshambo suicide = gson.fromJson(json, Roshambo.class); Roshambo suicide = gson.fromJson(json, Roshambo.class);
@ -140,7 +134,8 @@ public final class GraphAdapterBuilderTest {
.registerOn(gsonBuilder); .registerOn(gsonBuilder);
Gson gson = gsonBuilder.create(); Gson gson = gsonBuilder.create();
assertEquals("{'0x1':{'name':'Google','employees':['0x2','0x3']}," assertEquals(
"{'0x1':{'name':'Google','employees':['0x2','0x3']},"
+ "'0x2':{'name':'Jesse','company':'0x1'}," + "'0x2':{'name':'Jesse','company':'0x1'},"
+ "'0x3':{'name':'Joel','company':'0x1'}}", + "'0x3':{'name':'Joel','company':'0x1'}}",
gson.toJson(google).replace('"', '\'')); gson.toJson(google).replace('"', '\''));
@ -155,7 +150,8 @@ public final class GraphAdapterBuilderTest {
.registerOn(gsonBuilder); .registerOn(gsonBuilder);
Gson gson = gsonBuilder.create(); Gson gson = gsonBuilder.create();
String json = "{'0x1':{'name':'Google','employees':['0x2','0x3']}," String json =
"{'0x1':{'name':'Google','employees':['0x2','0x3']},"
+ "'0x2':{'name':'Jesse','company':'0x1'}," + "'0x2':{'name':'Jesse','company':'0x1'},"
+ "'0x3':{'name':'Joel','company':'0x1'}}"; + "'0x3':{'name':'Joel','company':'0x1'}}";
Company company = gson.fromJson(json, Company.class); Company company = gson.fromJson(json, Company.class);
@ -171,6 +167,7 @@ public final class GraphAdapterBuilderTest {
static class Roshambo { static class Roshambo {
String name; String name;
Roshambo beats; Roshambo beats;
Roshambo(String name) { Roshambo(String name) {
this.name = name; this.name = name;
} }
@ -179,6 +176,7 @@ public final class GraphAdapterBuilderTest {
static class Employee { static class Employee {
final String name; final String name;
final Company company; final Company company;
Employee(String name, Company company) { Employee(String name, Company company) {
this.name = name; this.name = name;
this.company = company; this.company = company;
@ -189,6 +187,7 @@ public final class GraphAdapterBuilderTest {
static class Company { static class Company {
final String name; final String name;
final List<Employee> employees = new ArrayList<>(); final List<Employee> employees = new ArrayList<>();
Company(String name) { Company(String name) {
this.name = name; this.name = name;
} }

View File

@ -46,7 +46,8 @@ public final class InterceptorTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
this.gson = new GsonBuilder() this.gson =
new GsonBuilder()
.registerTypeAdapterFactory(new InterceptorFactory()) .registerTypeAdapterFactory(new InterceptorFactory())
.enableComplexMapKeySerialization() .enableComplexMapKeySerialization()
.create(); .create();
@ -57,7 +58,8 @@ public final class InterceptorTest {
try { try {
gson.fromJson("{}", User.class); gson.fromJson("{}", User.class);
fail(); fail();
} catch (JsonParseException expected) {} } catch (JsonParseException expected) {
}
} }
@Test @Test
@ -68,26 +70,32 @@ public final class InterceptorTest {
@Test @Test
public void testList() { public void testList() {
List<User> list = gson.fromJson("[{name:'bob',password:'pwd'}]", new TypeToken<List<User>>(){}.getType()); List<User> list =
gson.fromJson("[{name:'bob',password:'pwd'}]", new TypeToken<List<User>>() {}.getType());
User user = list.get(0); User user = list.get(0);
assertEquals(User.DEFAULT_EMAIL, user.email); assertEquals(User.DEFAULT_EMAIL, user.email);
} }
@Test @Test
public void testCollection() { public void testCollection() {
Collection<User> list = gson.fromJson("[{name:'bob',password:'pwd'}]", new TypeToken<Collection<User>>(){}.getType()); Collection<User> list =
gson.fromJson(
"[{name:'bob',password:'pwd'}]", new TypeToken<Collection<User>>() {}.getType());
User user = list.iterator().next(); User user = list.iterator().next();
assertEquals(User.DEFAULT_EMAIL, user.email); assertEquals(User.DEFAULT_EMAIL, user.email);
} }
@Test @Test
public void testMapKeyAndValues() { public void testMapKeyAndValues() {
Type mapType = new TypeToken<Map<User, Address>>(){}.getType(); Type mapType = new TypeToken<Map<User, Address>>() {}.getType();
try { try {
gson.fromJson("[[{name:'bob',password:'pwd'},{}]]", mapType); gson.fromJson("[[{name:'bob',password:'pwd'},{}]]", mapType);
fail(); fail();
} catch (JsonSyntaxException expected) {} } catch (JsonSyntaxException expected) {
Map<User, Address> map = gson.fromJson("[[{name:'bob',password:'pwd'},{city:'Mountain View',state:'CA',zip:'94043'}]]", }
Map<User, Address> map =
gson.fromJson(
"[[{name:'bob',password:'pwd'},{city:'Mountain View',state:'CA',zip:'94043'}]]",
mapType); mapType);
Entry<User, Address> entry = map.entrySet().iterator().next(); Entry<User, Address> entry = map.entrySet().iterator().next();
assertEquals(User.DEFAULT_EMAIL, entry.getKey().email); assertEquals(User.DEFAULT_EMAIL, entry.getKey().email);
@ -102,13 +110,18 @@ public final class InterceptorTest {
@Test @Test
public void testCustomTypeAdapter() { public void testCustomTypeAdapter() {
Gson gson = new GsonBuilder() Gson gson =
.registerTypeAdapter(User.class, new TypeAdapter<User>() { new GsonBuilder()
@Override public void write(JsonWriter out, User value) throws IOException { .registerTypeAdapter(
User.class,
new TypeAdapter<User>() {
@Override
public void write(JsonWriter out, User value) throws IOException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override public User read(JsonReader in) throws IOException { @Override
public User read(JsonReader in) throws IOException {
in.beginObject(); in.beginObject();
String unused1 = in.nextName(); String unused1 = in.nextName();
String name = in.nextString(); String name = in.nextString();
@ -145,6 +158,7 @@ public final class InterceptorTest {
String password; String password;
String email; String email;
Address address; Address address;
public User(String name, String password) { public User(String name, String password) {
this.name = name; this.name = name;
this.password = password; this.password = password;
@ -152,7 +166,8 @@ public final class InterceptorTest {
} }
public static final class UserValidator implements JsonPostDeserializer<User> { public static final class UserValidator implements JsonPostDeserializer<User> {
@Override public void postDeserialize(User user) { @Override
public void postDeserialize(User user) {
if (user.name == null || user.password == null) { if (user.name == null || user.password == null) {
throw new JsonSyntaxException("name and password are required fields."); throw new JsonSyntaxException("name and password are required fields.");
} }
@ -172,7 +187,8 @@ public final class InterceptorTest {
} }
public static final class AddressValidator implements JsonPostDeserializer<Address> { public static final class AddressValidator implements JsonPostDeserializer<Address> {
@Override public void postDeserialize(Address address) { @Override
public void postDeserialize(Address address) {
if (address.city == null || address.state == null || address.zip == null) { if (address.city == null || address.state == null || address.zip == null) {
throw new JsonSyntaxException("Address city, state and zip are required fields."); throw new JsonSyntaxException("Address city, state and zip are required fields.");
} }

View File

@ -29,9 +29,8 @@ import org.junit.Test;
public class PostConstructAdapterFactoryTest { public class PostConstructAdapterFactoryTest {
@Test @Test
public void test() throws Exception { public void test() throws Exception {
Gson gson = new GsonBuilder() Gson gson =
.registerTypeAdapterFactory(new PostConstructAdapterFactory()) new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory()).create();
.create();
gson.fromJson("{\"bread\": \"white\", \"cheese\": \"cheddar\"}", Sandwich.class); gson.fromJson("{\"bread\": \"white\", \"cheese\": \"cheddar\"}", Sandwich.class);
try { try {
gson.fromJson("{\"bread\": \"cheesey bread\", \"cheese\": \"swiss\"}", Sandwich.class); gson.fromJson("{\"bread\": \"cheesey bread\", \"cheese\": \"swiss\"}", Sandwich.class);
@ -43,15 +42,19 @@ public class PostConstructAdapterFactoryTest {
@Test @Test
public void testList() { public void testList() {
MultipleSandwiches sandwiches = new MultipleSandwiches(Arrays.asList( MultipleSandwiches sandwiches =
new Sandwich("white", "cheddar"), new MultipleSandwiches(
new Sandwich("whole wheat", "swiss"))); Arrays.asList(new Sandwich("white", "cheddar"), new Sandwich("whole wheat", "swiss")));
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory()).create(); Gson gson =
new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory()).create();
// Throws NullPointerException without the fix in https://github.com/google/gson/pull/1103 // Throws NullPointerException without the fix in https://github.com/google/gson/pull/1103
String json = gson.toJson(sandwiches); String json = gson.toJson(sandwiches);
assertEquals("{\"sandwiches\":[{\"bread\":\"white\",\"cheese\":\"cheddar\"},{\"bread\":\"whole wheat\",\"cheese\":\"swiss\"}]}", json); assertEquals(
"{\"sandwiches\":[{\"bread\":\"white\",\"cheese\":\"cheddar\"},{\"bread\":\"whole"
+ " wheat\",\"cheese\":\"swiss\"}]}",
json);
MultipleSandwiches sandwichesFromJson = gson.fromJson(json, MultipleSandwiches.class); MultipleSandwiches sandwichesFromJson = gson.fromJson(json, MultipleSandwiches.class);
assertEquals(sandwiches, sandwichesFromJson); assertEquals(sandwiches, sandwichesFromJson);
@ -67,7 +70,8 @@ public class PostConstructAdapterFactoryTest {
this.cheese = cheese; this.cheese = cheese;
} }
@PostConstruct private void validate() { @PostConstruct
private void validate() {
if (bread.equals("cheesey bread") && cheese != null) { if (bread.equals("cheesey bread") && cheese != null) {
throw new IllegalArgumentException("too cheesey"); throw new IllegalArgumentException("too cheesey");
} }
@ -109,7 +113,9 @@ public class PostConstructAdapterFactoryTest {
return false; return false;
} }
final MultipleSandwiches other = (MultipleSandwiches) o; final MultipleSandwiches other = (MultipleSandwiches) o;
if (this.sandwiches == null ? other.sandwiches != null : !this.sandwiches.equals(other.sandwiches)) { if (this.sandwiches == null
? other.sandwiches != null
: !this.sandwiches.equals(other.sandwiches)) {
return false; return false;
} }
return true; return true;

View File

@ -31,18 +31,16 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testRuntimeTypeAdapter() { public void testRuntimeTypeAdapter() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of( RuntimeTypeAdapterFactory<BillingInstrument> rta =
BillingInstrument.class) RuntimeTypeAdapterFactory.of(BillingInstrument.class).registerSubtype(CreditCard.class);
.registerSubtype(CreditCard.class); Gson gson = new GsonBuilder().registerTypeAdapterFactory(rta).create();
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(rta)
.create();
CreditCard original = new CreditCard("Jesse", 234); CreditCard original = new CreditCard("Jesse", 234);
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}", assertEquals(
"{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
gson.toJson(original, BillingInstrument.class)); gson.toJson(original, BillingInstrument.class));
BillingInstrument deserialized = gson.fromJson( BillingInstrument deserialized =
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class); gson.fromJson("{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName); assertEquals("Jesse", deserialized.ownerName);
assertTrue(deserialized instanceof CreditCard); assertTrue(deserialized instanceof CreditCard);
} }
@ -52,37 +50,34 @@ public final class RuntimeTypeAdapterFactoryTest {
// We don't have an explicit factory for CreditCard.class, but we do have one for // We don't have an explicit factory for CreditCard.class, but we do have one for
// BillingInstrument.class that has recognizeSubtypes(). So it should recognize CreditCard, and // BillingInstrument.class that has recognizeSubtypes(). So it should recognize CreditCard, and
// when we call gson.toJson(original) below, without an explicit type, it should be invoked. // when we call gson.toJson(original) below, without an explicit type, it should be invoked.
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of( RuntimeTypeAdapterFactory<BillingInstrument> rta =
BillingInstrument.class) RuntimeTypeAdapterFactory.of(BillingInstrument.class)
.recognizeSubtypes() .recognizeSubtypes()
.registerSubtype(CreditCard.class); .registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder().registerTypeAdapterFactory(rta).create();
.registerTypeAdapterFactory(rta)
.create();
CreditCard original = new CreditCard("Jesse", 234); CreditCard original = new CreditCard("Jesse", 234);
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}", assertEquals(
gson.toJson(original)); "{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}", gson.toJson(original));
BillingInstrument deserialized = gson.fromJson( BillingInstrument deserialized =
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class); gson.fromJson("{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName); assertEquals("Jesse", deserialized.ownerName);
assertTrue(deserialized instanceof CreditCard); assertTrue(deserialized instanceof CreditCard);
} }
@Test @Test
public void testRuntimeTypeIsBaseType() { public void testRuntimeTypeIsBaseType() {
TypeAdapterFactory rta = RuntimeTypeAdapterFactory.of( TypeAdapterFactory rta =
BillingInstrument.class) RuntimeTypeAdapterFactory.of(BillingInstrument.class)
.registerSubtype(BillingInstrument.class); .registerSubtype(BillingInstrument.class);
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder().registerTypeAdapterFactory(rta).create();
.registerTypeAdapterFactory(rta)
.create();
BillingInstrument original = new BillingInstrument("Jesse"); BillingInstrument original = new BillingInstrument("Jesse");
assertEquals("{\"type\":\"BillingInstrument\",\"ownerName\":\"Jesse\"}", assertEquals(
"{\"type\":\"BillingInstrument\",\"ownerName\":\"Jesse\"}",
gson.toJson(original, BillingInstrument.class)); gson.toJson(original, BillingInstrument.class));
BillingInstrument deserialized = gson.fromJson( BillingInstrument deserialized =
"{type:'BillingInstrument',ownerName:'Jesse'}", BillingInstrument.class); gson.fromJson("{type:'BillingInstrument',ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName); assertEquals("Jesse", deserialized.ownerName);
} }
@ -106,8 +101,8 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testNullSubtype() { public void testNullSubtype() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of( RuntimeTypeAdapterFactory<BillingInstrument> rta =
BillingInstrument.class); RuntimeTypeAdapterFactory.of(BillingInstrument.class);
try { try {
rta.registerSubtype(null); rta.registerSubtype(null);
fail(); fail();
@ -117,8 +112,8 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testNullLabel() { public void testNullLabel() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of( RuntimeTypeAdapterFactory<BillingInstrument> rta =
BillingInstrument.class); RuntimeTypeAdapterFactory.of(BillingInstrument.class);
try { try {
rta.registerSubtype(CreditCard.class, null); rta.registerSubtype(CreditCard.class, null);
fail(); fail();
@ -128,8 +123,8 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testDuplicateSubtype() { public void testDuplicateSubtype() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of( RuntimeTypeAdapterFactory<BillingInstrument> rta =
BillingInstrument.class); RuntimeTypeAdapterFactory.of(BillingInstrument.class);
rta.registerSubtype(CreditCard.class, "CC"); rta.registerSubtype(CreditCard.class, "CC");
try { try {
rta.registerSubtype(CreditCard.class, "Visa"); rta.registerSubtype(CreditCard.class, "Visa");
@ -140,8 +135,8 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testDuplicateLabel() { public void testDuplicateLabel() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of( RuntimeTypeAdapterFactory<BillingInstrument> rta =
BillingInstrument.class); RuntimeTypeAdapterFactory.of(BillingInstrument.class);
rta.registerSubtype(CreditCard.class, "CC"); rta.registerSubtype(CreditCard.class, "CC");
try { try {
rta.registerSubtype(BankTransfer.class, "CC"); rta.registerSubtype(BankTransfer.class, "CC");
@ -152,11 +147,9 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testDeserializeMissingTypeField() { public void testDeserializeMissingTypeField() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class) TypeAdapterFactory billingAdapter =
.registerSubtype(CreditCard.class); RuntimeTypeAdapterFactory.of(BillingInstrument.class).registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder().registerTypeAdapterFactory(billingAdapter).create();
.registerTypeAdapterFactory(billingAdapter)
.create();
try { try {
gson.fromJson("{ownerName:'Jesse'}", BillingInstrument.class); gson.fromJson("{ownerName:'Jesse'}", BillingInstrument.class);
fail(); fail();
@ -166,11 +159,9 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testDeserializeMissingSubtype() { public void testDeserializeMissingSubtype() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class) TypeAdapterFactory billingAdapter =
.registerSubtype(BankTransfer.class); RuntimeTypeAdapterFactory.of(BillingInstrument.class).registerSubtype(BankTransfer.class);
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder().registerTypeAdapterFactory(billingAdapter).create();
.registerTypeAdapterFactory(billingAdapter)
.create();
try { try {
gson.fromJson("{type:'CreditCard',ownerName:'Jesse'}", BillingInstrument.class); gson.fromJson("{type:'CreditCard',ownerName:'Jesse'}", BillingInstrument.class);
fail(); fail();
@ -180,11 +171,9 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testSerializeMissingSubtype() { public void testSerializeMissingSubtype() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class) TypeAdapterFactory billingAdapter =
.registerSubtype(BankTransfer.class); RuntimeTypeAdapterFactory.of(BillingInstrument.class).registerSubtype(BankTransfer.class);
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder().registerTypeAdapterFactory(billingAdapter).create();
.registerTypeAdapterFactory(billingAdapter)
.create();
try { try {
gson.toJson(new CreditCard("Jesse", 456), BillingInstrument.class); gson.toJson(new CreditCard("Jesse", 456), BillingInstrument.class);
fail(); fail();
@ -194,11 +183,10 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testSerializeCollidingTypeFieldName() { public void testSerializeCollidingTypeFieldName() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class, "cvv") TypeAdapterFactory billingAdapter =
RuntimeTypeAdapterFactory.of(BillingInstrument.class, "cvv")
.registerSubtype(CreditCard.class); .registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder().registerTypeAdapterFactory(billingAdapter).create();
.registerTypeAdapterFactory(billingAdapter)
.create();
try { try {
gson.toJson(new CreditCard("Jesse", 456), BillingInstrument.class); gson.toJson(new CreditCard("Jesse", 456), BillingInstrument.class);
fail(); fail();
@ -208,19 +196,21 @@ public final class RuntimeTypeAdapterFactoryTest {
@Test @Test
public void testSerializeWrappedNullValue() { public void testSerializeWrappedNullValue() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class) TypeAdapterFactory billingAdapter =
RuntimeTypeAdapterFactory.of(BillingInstrument.class)
.registerSubtype(CreditCard.class) .registerSubtype(CreditCard.class)
.registerSubtype(BankTransfer.class); .registerSubtype(BankTransfer.class);
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder().registerTypeAdapterFactory(billingAdapter).create();
.registerTypeAdapterFactory(billingAdapter) String serialized =
.create(); gson.toJson(new BillingInstrumentWrapper(null), BillingInstrumentWrapper.class);
String serialized = gson.toJson(new BillingInstrumentWrapper(null), BillingInstrumentWrapper.class); BillingInstrumentWrapper deserialized =
BillingInstrumentWrapper deserialized = gson.fromJson(serialized, BillingInstrumentWrapper.class); gson.fromJson(serialized, BillingInstrumentWrapper.class);
assertNull(deserialized.instrument); assertNull(deserialized.instrument);
} }
static class BillingInstrumentWrapper { static class BillingInstrumentWrapper {
BillingInstrument instrument; BillingInstrument instrument;
BillingInstrumentWrapper(BillingInstrument instrument) { BillingInstrumentWrapper(BillingInstrument instrument) {
this.instrument = instrument; this.instrument = instrument;
} }
@ -228,6 +218,7 @@ public final class RuntimeTypeAdapterFactoryTest {
static class BillingInstrument { static class BillingInstrument {
private final String ownerName; private final String ownerName;
BillingInstrument(String ownerName) { BillingInstrument(String ownerName) {
this.ownerName = ownerName; this.ownerName = ownerName;
} }
@ -235,6 +226,7 @@ public final class RuntimeTypeAdapterFactoryTest {
static class CreditCard extends BillingInstrument { static class CreditCard extends BillingInstrument {
int cvv; int cvv;
CreditCard(String ownerName, int cvv) { CreditCard(String ownerName, int cvv) {
super(ownerName); super(ownerName);
this.cvv = cvv; this.cvv = cvv;
@ -243,6 +235,7 @@ public final class RuntimeTypeAdapterFactoryTest {
static class BankTransfer extends BillingInstrument { static class BankTransfer extends BillingInstrument {
int bankAccount; int bankAccount;
BankTransfer(String ownerName, int bankAccount) { BankTransfer(String ownerName, int bankAccount) {
super(ownerName); super(ownerName);
this.bankAccount = bankAccount; this.bankAccount = bankAccount;

View File

@ -31,9 +31,8 @@ import org.junit.Test;
@SuppressWarnings("JavaUtilDate") @SuppressWarnings("JavaUtilDate")
public final class UtcDateTypeAdapterTest { public final class UtcDateTypeAdapterTest {
private final Gson gson = new GsonBuilder() private final Gson gson =
.registerTypeAdapter(Date.class, new UtcDateTypeAdapter()) new GsonBuilder().registerTypeAdapter(Date.class, new UtcDateTypeAdapter()).create();
.create();
@Test @Test
public void testLocalTimeZone() { public void testLocalTimeZone() {
@ -56,21 +55,21 @@ public final class UtcDateTypeAdapterTest {
} }
/** /**
* JDK 1.7 introduced support for XXX format to indicate UTC date. But Android is older JDK. * JDK 1.7 introduced support for XXX format to indicate UTC date. But Android is older JDK. We
* We want to make sure that this date is parseable in Android. * want to make sure that this date is parseable in Android.
*/ */
@Test @Test
public void testUtcDatesOnJdkBefore1_7() { public void testUtcDatesOnJdkBefore1_7() {
Gson gson = new GsonBuilder() Gson gson =
.registerTypeAdapter(Date.class, new UtcDateTypeAdapter()) new GsonBuilder().registerTypeAdapter(Date.class, new UtcDateTypeAdapter()).create();
.create();
Date unused = gson.fromJson("'2014-12-05T04:00:00.000Z'", Date.class); Date unused = gson.fromJson("'2014-12-05T04:00:00.000Z'", Date.class);
} }
@Test @Test
public void testUtcWithJdk7Default() { public void testUtcWithJdk7Default() {
Date expected = new Date(); Date expected = new Date();
SimpleDateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.US); SimpleDateFormat iso8601Format =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.US);
iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC")); iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
String expectedJson = "\"" + iso8601Format.format(expected) + "\""; String expectedJson = "\"" + iso8601Format.format(expected) + "\"";
String actualJson = gson.toJson(expected); String actualJson = gson.toJson(expected);
@ -91,7 +90,9 @@ public final class UtcDateTypeAdapterTest {
gson.fromJson("2017-06-20T14:32:30", Date.class); gson.fromJson("2017-06-20T14:32:30", Date.class);
fail("No exception"); fail("No exception");
} catch (JsonParseException exe) { } catch (JsonParseException exe) {
assertEquals("java.text.ParseException: Failed to parse date ['2017-06-20T14']: 2017-06-20T14", exe.getMessage()); assertEquals(
"java.text.ParseException: Failed to parse date ['2017-06-20T14']: 2017-06-20T14",
exe.getMessage());
} }
} }
} }

View File

@ -28,8 +28,7 @@ import java.io.IOException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class Java17RecordReflectionTest { class Java17RecordReflectionTest {
public record PublicRecord(int i) { public record PublicRecord(int i) {}
}
@Test @Test
void testPublicRecord() { void testPublicRecord() {
@ -39,8 +38,7 @@ class Java17RecordReflectionTest {
} }
// Private record has implicit private canonical constructor // Private record has implicit private canonical constructor
private record PrivateRecord(int i) { private record PrivateRecord(int i) {}
}
@Test @Test
void testPrivateRecord() { void testPrivateRecord() {
@ -51,8 +49,7 @@ class Java17RecordReflectionTest {
@Test @Test
void testLocalRecord() { void testLocalRecord() {
record LocalRecordDeserialization(int i) { record LocalRecordDeserialization(int i) {}
}
Gson gson = new Gson(); Gson gson = new Gson();
LocalRecordDeserialization r = gson.fromJson("{\"i\":1}", LocalRecordDeserialization.class); LocalRecordDeserialization r = gson.fromJson("{\"i\":1}", LocalRecordDeserialization.class);
@ -61,20 +58,19 @@ class Java17RecordReflectionTest {
@Test @Test
void testLocalRecordSerialization() { void testLocalRecordSerialization() {
record LocalRecordSerialization(int i) { record LocalRecordSerialization(int i) {}
}
Gson gson = new Gson(); Gson gson = new Gson();
assertThat(gson.toJson(new LocalRecordSerialization(1))).isEqualTo("{\"i\":1}"); assertThat(gson.toJson(new LocalRecordSerialization(1))).isEqualTo("{\"i\":1}");
} }
private record RecordWithSerializedName(@SerializedName("custom-name") int i) { private record RecordWithSerializedName(@SerializedName("custom-name") int i) {}
}
@Test @Test
void testSerializedName() { void testSerializedName() {
Gson gson = new Gson(); Gson gson = new Gson();
RecordWithSerializedName r = gson.fromJson("{\"custom-name\":1}", RecordWithSerializedName.class); RecordWithSerializedName r =
gson.fromJson("{\"custom-name\":1}", RecordWithSerializedName.class);
assertThat(r.i).isEqualTo(1); assertThat(r.i).isEqualTo(1);
assertThat(gson.toJson(new RecordWithSerializedName(2))).isEqualTo("{\"custom-name\":2}"); assertThat(gson.toJson(new RecordWithSerializedName(2))).isEqualTo("{\"custom-name\":2}");
@ -133,9 +129,7 @@ class Java17RecordReflectionTest {
} }
private record RecordWithCustomFieldAdapter( private record RecordWithCustomFieldAdapter(
@JsonAdapter(RecordWithCustomFieldAdapter.CustomAdapter.class) @JsonAdapter(RecordWithCustomFieldAdapter.CustomAdapter.class) int i) {
int i
) {
private static class CustomAdapter extends TypeAdapter<Integer> { private static class CustomAdapter extends TypeAdapter<Integer> {
@Override @Override
public Integer read(JsonReader in) throws IOException { public Integer read(JsonReader in) throws IOException {
@ -158,20 +152,23 @@ class Java17RecordReflectionTest {
assertThat(gson.toJson(new RecordWithCustomFieldAdapter(1))).isEqualTo("{\"i\":7}"); assertThat(gson.toJson(new RecordWithCustomFieldAdapter(1))).isEqualTo("{\"i\":7}");
} }
private record RecordWithRegisteredAdapter(int i) { private record RecordWithRegisteredAdapter(int i) {}
}
@Test @Test
void testCustomAdapter() { void testCustomAdapter() {
Gson gson = new GsonBuilder() Gson gson =
.registerTypeAdapter(RecordWithRegisteredAdapter.class, new TypeAdapter<RecordWithRegisteredAdapter>() { new GsonBuilder()
.registerTypeAdapter(
RecordWithRegisteredAdapter.class,
new TypeAdapter<RecordWithRegisteredAdapter>() {
@Override @Override
public RecordWithRegisteredAdapter read(JsonReader in) throws IOException { public RecordWithRegisteredAdapter read(JsonReader in) throws IOException {
return new RecordWithRegisteredAdapter(in.nextInt() + 5); return new RecordWithRegisteredAdapter(in.nextInt() + 5);
} }
@Override @Override
public void write(JsonWriter out, RecordWithRegisteredAdapter value) throws IOException { public void write(JsonWriter out, RecordWithRegisteredAdapter value)
throws IOException {
out.value(value.i + 6); out.value(value.i + 6);
} }
}) })

View File

@ -56,7 +56,8 @@ class ReflectionTest {
void testCustomDefaultConstructor() { void testCustomDefaultConstructor() {
Gson gson = new Gson(); Gson gson = new Gson();
ClassWithCustomDefaultConstructor c = gson.fromJson("{\"i\":2}", ClassWithCustomDefaultConstructor.class); ClassWithCustomDefaultConstructor c =
gson.fromJson("{\"i\":2}", ClassWithCustomDefaultConstructor.class);
assertThat(c.i).isEqualTo(2); assertThat(c.i).isEqualTo(2);
c = gson.fromJson("{}", ClassWithCustomDefaultConstructor.class); c = gson.fromJson("{}", ClassWithCustomDefaultConstructor.class);
@ -75,26 +76,32 @@ class ReflectionTest {
/** /**
* Tests deserializing a class without default constructor. * Tests deserializing a class without default constructor.
* *
* <p>This should use JDK Unsafe, and would normally require specifying {@code "unsafeAllocated": true} * <p>This should use JDK Unsafe, and would normally require specifying {@code "unsafeAllocated":
* in the reflection metadata for GraalVM, though for some reason it also seems to work without it? Possibly * true} in the reflection metadata for GraalVM, though for some reason it also seems to work
* because GraalVM seems to have special support for Gson, see its class {@code com.oracle.svm.thirdparty.gson.GsonFeature}. * without it? Possibly because GraalVM seems to have special support for Gson, see its class
* {@code com.oracle.svm.thirdparty.gson.GsonFeature}.
*/ */
@Test @Test
void testClassWithoutDefaultConstructor() { void testClassWithoutDefaultConstructor() {
Gson gson = new Gson(); Gson gson = new Gson();
ClassWithoutDefaultConstructor c = gson.fromJson("{\"i\":1}", ClassWithoutDefaultConstructor.class); ClassWithoutDefaultConstructor c =
gson.fromJson("{\"i\":1}", ClassWithoutDefaultConstructor.class);
assertThat(c.i).isEqualTo(1); assertThat(c.i).isEqualTo(1);
c = gson.fromJson("{}", ClassWithoutDefaultConstructor.class); c = gson.fromJson("{}", ClassWithoutDefaultConstructor.class);
// Class is instantiated with JDK Unsafe, so field keeps its default value instead of assigned -1 // Class is instantiated with JDK Unsafe, so field keeps its default value instead of assigned
// -1
assertThat(c.i).isEqualTo(0); assertThat(c.i).isEqualTo(0);
} }
@Test @Test
void testInstanceCreator() { void testInstanceCreator() {
Gson gson = new GsonBuilder() Gson gson =
.registerTypeAdapter(ClassWithoutDefaultConstructor.class, new InstanceCreator<ClassWithoutDefaultConstructor>() { new GsonBuilder()
.registerTypeAdapter(
ClassWithoutDefaultConstructor.class,
new InstanceCreator<ClassWithoutDefaultConstructor>() {
@Override @Override
public ClassWithoutDefaultConstructor createInstance(Type type) { public ClassWithoutDefaultConstructor createInstance(Type type) {
return new ClassWithoutDefaultConstructor(-2); return new ClassWithoutDefaultConstructor(-2);
@ -102,7 +109,8 @@ class ReflectionTest {
}) })
.create(); .create();
ClassWithoutDefaultConstructor c = gson.fromJson("{\"i\":1}", ClassWithoutDefaultConstructor.class); ClassWithoutDefaultConstructor c =
gson.fromJson("{\"i\":1}", ClassWithoutDefaultConstructor.class);
assertThat(c.i).isEqualTo(1); assertThat(c.i).isEqualTo(1);
c = gson.fromJson("{}", ClassWithoutDefaultConstructor.class); c = gson.fromJson("{}", ClassWithoutDefaultConstructor.class);
@ -220,15 +228,19 @@ class ReflectionTest {
@Test @Test
void testCustomAdapter() { void testCustomAdapter() {
Gson gson = new GsonBuilder() Gson gson =
.registerTypeAdapter(ClassWithRegisteredAdapter.class, new TypeAdapter<ClassWithRegisteredAdapter>() { new GsonBuilder()
.registerTypeAdapter(
ClassWithRegisteredAdapter.class,
new TypeAdapter<ClassWithRegisteredAdapter>() {
@Override @Override
public ClassWithRegisteredAdapter read(JsonReader in) throws IOException { public ClassWithRegisteredAdapter read(JsonReader in) throws IOException {
return new ClassWithRegisteredAdapter(in.nextInt() + 5); return new ClassWithRegisteredAdapter(in.nextInt() + 5);
} }
@Override @Override
public void write(JsonWriter out, ClassWithRegisteredAdapter value) throws IOException { public void write(JsonWriter out, ClassWithRegisteredAdapter value)
throws IOException {
out.value(value.i + 6); out.value(value.i + 6);
} }
}) })
@ -244,12 +256,17 @@ class ReflectionTest {
void testGenerics() { void testGenerics() {
Gson gson = new Gson(); Gson gson = new Gson();
List<ClassWithDefaultConstructor> list = gson.fromJson("[{\"i\":1}]", new TypeToken<List<ClassWithDefaultConstructor>>() {}); List<ClassWithDefaultConstructor> list =
gson.fromJson("[{\"i\":1}]", new TypeToken<List<ClassWithDefaultConstructor>>() {});
assertThat(list).hasSize(1); assertThat(list).hasSize(1);
assertThat(list.get(0).i).isEqualTo(1); assertThat(list.get(0).i).isEqualTo(1);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<ClassWithDefaultConstructor> list2 = (List<ClassWithDefaultConstructor>) gson.fromJson("[{\"i\":1}]", TypeToken.getParameterized(List.class, ClassWithDefaultConstructor.class)); List<ClassWithDefaultConstructor> list2 =
(List<ClassWithDefaultConstructor>)
gson.fromJson(
"[{\"i\":1}]",
TypeToken.getParameterized(List.class, ClassWithDefaultConstructor.class));
assertThat(list2).hasSize(1); assertThat(list2).hasSize(1);
assertThat(list2.get(0).i).isEqualTo(1); assertThat(list2.get(0).i).isEqualTo(1);
} }

View File

@ -17,12 +17,13 @@
package com.google.gson; package com.google.gson;
/** /**
* A strategy (or policy) definition that is used to decide whether or not a field or * A strategy (or policy) definition that is used to decide whether or not a field or class should
* class should be serialized or deserialized as part of the JSON output/input. * be serialized or deserialized as part of the JSON output/input.
* *
* <p>The following are a few examples that shows how you can use this exclusion mechanism. * <p>The following are a few examples that shows how you can use this exclusion mechanism.
* *
* <p><strong>Exclude fields and objects based on a particular class type:</strong> * <p><strong>Exclude fields and objects based on a particular class type:</strong>
*
* <pre class="code"> * <pre class="code">
* private static class SpecificClassExclusionStrategy implements ExclusionStrategy { * private static class SpecificClassExclusionStrategy implements ExclusionStrategy {
* private final Class&lt;?&gt; excludedThisClass; * private final Class&lt;?&gt; excludedThisClass;
@ -42,6 +43,7 @@ package com.google.gson;
* </pre> * </pre>
* *
* <p><strong>Excludes fields and objects based on a particular annotation:</strong> * <p><strong>Excludes fields and objects based on a particular annotation:</strong>
*
* <pre class="code"> * <pre class="code">
* public &#64;interface FooAnnotation { * public &#64;interface FooAnnotation {
* // some implementation here * // some implementation here
@ -59,9 +61,10 @@ package com.google.gson;
* } * }
* </pre> * </pre>
* *
* <p>Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then * <p>Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then the
* the {@code GsonBuilder} is required. The following is an example of how you can use the * {@code GsonBuilder} is required. The following is an example of how you can use the {@code
* {@code GsonBuilder} to configure Gson to use one of the above samples: * GsonBuilder} to configure Gson to use one of the above samples:
*
* <pre class="code"> * <pre class="code">
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class); * ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
* Gson gson = new GsonBuilder() * Gson gson = new GsonBuilder()
@ -70,10 +73,10 @@ package com.google.gson;
* </pre> * </pre>
* *
* <p>For certain model classes, you may only want to serialize a field, but exclude it for * <p>For certain model classes, you may only want to serialize a field, but exclude it for
* deserialization. To do that, you can write an {@code ExclusionStrategy} as per normal; * deserialization. To do that, you can write an {@code ExclusionStrategy} as per normal; however,
* however, you would register it with the * you would register it with the {@link
* {@link GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)} method. * GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)} method. For example:
* For example: *
* <pre class="code"> * <pre class="code">
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class); * ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
* Gson gson = new GsonBuilder() * Gson gson = new GsonBuilder()
@ -83,11 +86,9 @@ package com.google.gson;
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*
* @see GsonBuilder#setExclusionStrategies(ExclusionStrategy...) * @see GsonBuilder#setExclusionStrategies(ExclusionStrategy...)
* @see GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy) * @see GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)
* @see GsonBuilder#addSerializationExclusionStrategy(ExclusionStrategy) * @see GsonBuilder#addSerializationExclusionStrategy(ExclusionStrategy)
*
* @since 1.4 * @since 1.4
*/ */
public interface ExclusionStrategy { public interface ExclusionStrategy {

View File

@ -30,7 +30,6 @@ import java.util.Objects;
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*
* @since 1.4 * @since 1.4
*/ */
public final class FieldAttributes { public final class FieldAttributes {
@ -67,6 +66,7 @@ public final class FieldAttributes {
* Returns the declared generic type of the field. * Returns the declared generic type of the field.
* *
* <p>For example, assume the following class definition: * <p>For example, assume the following class definition:
*
* <pre class="code"> * <pre class="code">
* public class Foo { * public class Foo {
* private String bar; * private String bar;
@ -76,8 +76,8 @@ public final class FieldAttributes {
* Type listParameterizedType = new TypeToken&lt;List&lt;String&gt;&gt;() {}.getType(); * Type listParameterizedType = new TypeToken&lt;List&lt;String&gt;&gt;() {}.getType();
* </pre> * </pre>
* *
* <p>This method would return {@code String.class} for the {@code bar} field and * <p>This method would return {@code String.class} for the {@code bar} field and {@code
* {@code listParameterizedType} for the {@code red} field. * listParameterizedType} for the {@code red} field.
* *
* @return the specific type declared for this field * @return the specific type declared for this field
*/ */
@ -89,6 +89,7 @@ public final class FieldAttributes {
* Returns the {@code Class} object that was declared for this field. * Returns the {@code Class} object that was declared for this field.
* *
* <p>For example, assume the following class definition: * <p>For example, assume the following class definition:
*
* <pre class="code"> * <pre class="code">
* public class Foo { * public class Foo {
* private String bar; * private String bar;
@ -96,8 +97,8 @@ public final class FieldAttributes {
* } * }
* </pre> * </pre>
* *
* <p>This method would return {@code String.class} for the {@code bar} field and * <p>This method would return {@code String.class} for the {@code bar} field and {@code
* {@code List.class} for the {@code red} field. * List.class} for the {@code red} field.
* *
* @return the specific class object that was declared for the field * @return the specific class object that was declared for the field
*/ */
@ -106,8 +107,8 @@ public final class FieldAttributes {
} }
/** /**
* Returns the {@code T} annotation object from this field if it exists; otherwise returns * Returns the {@code T} annotation object from this field if it exists; otherwise returns {@code
* {@code null}. * null}.
* *
* @param annotation the class of the annotation that will be retrieved * @param annotation the class of the annotation that will be retrieved
* @return the annotation instance if it is bound to the field; otherwise {@code null} * @return the annotation instance if it is bound to the field; otherwise {@code null}
@ -130,6 +131,7 @@ public final class FieldAttributes {
* Returns {@code true} if the field is defined with the {@code modifier}. * Returns {@code true} if the field is defined with the {@code modifier}.
* *
* <p>This method is meant to be called as: * <p>This method is meant to be called as:
*
* <pre class="code"> * <pre class="code">
* boolean hasPublicModifier = fieldAttribute.hasModifier(java.lang.reflect.Modifier.PUBLIC); * boolean hasPublicModifier = fieldAttribute.hasModifier(java.lang.reflect.Modifier.PUBLIC);
* </pre> * </pre>

View File

@ -20,150 +20,161 @@ import java.lang.reflect.Field;
import java.util.Locale; import java.util.Locale;
/** /**
* An enumeration that defines a few standard naming conventions for JSON field names. * An enumeration that defines a few standard naming conventions for JSON field names. This
* This enumeration should be used in conjunction with {@link com.google.gson.GsonBuilder} * enumeration should be used in conjunction with {@link com.google.gson.GsonBuilder} to configure a
* to configure a {@link com.google.gson.Gson} instance to properly translate Java field * {@link com.google.gson.Gson} instance to properly translate Java field names into the desired
* names into the desired JSON field names. * JSON field names.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*/ */
public enum FieldNamingPolicy implements FieldNamingStrategy { public enum FieldNamingPolicy implements FieldNamingStrategy {
/** /** Using this naming policy with Gson will ensure that the field name is unchanged. */
* Using this naming policy with Gson will ensure that the field name is
* unchanged.
*/
IDENTITY() { IDENTITY() {
@Override public String translateName(Field f) { @Override
public String translateName(Field f) {
return f.getName(); return f.getName();
} }
}, },
/** /**
* Using this naming policy with Gson will ensure that the first "letter" of the Java * Using this naming policy with Gson will ensure that the first "letter" of the Java field name
* field name is capitalized when serialized to its JSON form. * is capitalized when serialized to its JSON form.
*
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":
* *
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":</p>
* <ul> * <ul>
* <li>someFieldName ---&gt; SomeFieldName</li> * <li>someFieldName ---&gt; SomeFieldName
* <li>_someFieldName ---&gt; _SomeFieldName</li> * <li>_someFieldName ---&gt; _SomeFieldName
* </ul> * </ul>
*/ */
UPPER_CAMEL_CASE() { UPPER_CAMEL_CASE() {
@Override public String translateName(Field f) { @Override
public String translateName(Field f) {
return upperCaseFirstLetter(f.getName()); return upperCaseFirstLetter(f.getName());
} }
}, },
/** /**
* Using this naming policy with Gson will ensure that the first "letter" of the Java * Using this naming policy with Gson will ensure that the first "letter" of the Java field name
* field name is capitalized when serialized to its JSON form and the words will be * is capitalized when serialized to its JSON form and the words will be separated by a space.
* separated by a space. *
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":
* *
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":</p>
* <ul> * <ul>
* <li>someFieldName ---&gt; Some Field Name</li> * <li>someFieldName ---&gt; Some Field Name
* <li>_someFieldName ---&gt; _Some Field Name</li> * <li>_someFieldName ---&gt; _Some Field Name
* </ul> * </ul>
* *
* @since 1.4 * @since 1.4
*/ */
UPPER_CAMEL_CASE_WITH_SPACES() { UPPER_CAMEL_CASE_WITH_SPACES() {
@Override public String translateName(Field f) { @Override
public String translateName(Field f) {
return upperCaseFirstLetter(separateCamelCase(f.getName(), ' ')); return upperCaseFirstLetter(separateCamelCase(f.getName(), ' '));
} }
}, },
/** /**
* Using this naming policy with Gson will modify the Java Field name from its camel cased * Using this naming policy with Gson will modify the Java Field name from its camel cased form to
* form to an upper case field name where each word is separated by an underscore (_). * an upper case field name where each word is separated by an underscore (_).
*
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":
* *
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":</p>
* <ul> * <ul>
* <li>someFieldName ---&gt; SOME_FIELD_NAME</li> * <li>someFieldName ---&gt; SOME_FIELD_NAME
* <li>_someFieldName ---&gt; _SOME_FIELD_NAME</li> * <li>_someFieldName ---&gt; _SOME_FIELD_NAME
* <li>aStringField ---&gt; A_STRING_FIELD</li> * <li>aStringField ---&gt; A_STRING_FIELD
* <li>aURL ---&gt; A_U_R_L</li> * <li>aURL ---&gt; A_U_R_L
* </ul> * </ul>
* *
* @since 2.9.0 * @since 2.9.0
*/ */
UPPER_CASE_WITH_UNDERSCORES() { UPPER_CASE_WITH_UNDERSCORES() {
@Override public String translateName(Field f) { @Override
public String translateName(Field f) {
return separateCamelCase(f.getName(), '_').toUpperCase(Locale.ENGLISH); return separateCamelCase(f.getName(), '_').toUpperCase(Locale.ENGLISH);
} }
}, },
/** /**
* Using this naming policy with Gson will modify the Java Field name from its camel cased * Using this naming policy with Gson will modify the Java Field name from its camel cased form to
* form to a lower case field name where each word is separated by an underscore (_). * a lower case field name where each word is separated by an underscore (_).
*
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":
* *
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":</p>
* <ul> * <ul>
* <li>someFieldName ---&gt; some_field_name</li> * <li>someFieldName ---&gt; some_field_name
* <li>_someFieldName ---&gt; _some_field_name</li> * <li>_someFieldName ---&gt; _some_field_name
* <li>aStringField ---&gt; a_string_field</li> * <li>aStringField ---&gt; a_string_field
* <li>aURL ---&gt; a_u_r_l</li> * <li>aURL ---&gt; a_u_r_l
* </ul> * </ul>
*/ */
LOWER_CASE_WITH_UNDERSCORES() { LOWER_CASE_WITH_UNDERSCORES() {
@Override public String translateName(Field f) { @Override
public String translateName(Field f) {
return separateCamelCase(f.getName(), '_').toLowerCase(Locale.ENGLISH); return separateCamelCase(f.getName(), '_').toLowerCase(Locale.ENGLISH);
} }
}, },
/** /**
* Using this naming policy with Gson will modify the Java Field name from its camel cased * Using this naming policy with Gson will modify the Java Field name from its camel cased form to
* form to a lower case field name where each word is separated by a dash (-). * a lower case field name where each word is separated by a dash (-).
*
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":
* *
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":</p>
* <ul> * <ul>
* <li>someFieldName ---&gt; some-field-name</li> * <li>someFieldName ---&gt; some-field-name
* <li>_someFieldName ---&gt; _some-field-name</li> * <li>_someFieldName ---&gt; _some-field-name
* <li>aStringField ---&gt; a-string-field</li> * <li>aStringField ---&gt; a-string-field
* <li>aURL ---&gt; a-u-r-l</li> * <li>aURL ---&gt; a-u-r-l
* </ul> * </ul>
*
* Using dashes in JavaScript is not recommended since dash is also used for a minus sign in * Using dashes in JavaScript is not recommended since dash is also used for a minus sign in
* expressions. This requires that a field named with dashes is always accessed as a quoted * expressions. This requires that a field named with dashes is always accessed as a quoted
* property like {@code myobject['my-field']}. Accessing it as an object field * property like {@code myobject['my-field']}. Accessing it as an object field {@code
* {@code myobject.my-field} will result in an unintended JavaScript expression. * myobject.my-field} will result in an unintended JavaScript expression.
* *
* @since 1.4 * @since 1.4
*/ */
LOWER_CASE_WITH_DASHES() { LOWER_CASE_WITH_DASHES() {
@Override public String translateName(Field f) { @Override
public String translateName(Field f) {
return separateCamelCase(f.getName(), '-').toLowerCase(Locale.ENGLISH); return separateCamelCase(f.getName(), '-').toLowerCase(Locale.ENGLISH);
} }
}, },
/** /**
* Using this naming policy with Gson will modify the Java Field name from its camel cased * Using this naming policy with Gson will modify the Java Field name from its camel cased form to
* form to a lower case field name where each word is separated by a dot (.). * a lower case field name where each word is separated by a dot (.).
*
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":
* *
* <p>Here are a few examples of the form "Java Field Name" ---&gt; "JSON Field Name":</p>
* <ul> * <ul>
* <li>someFieldName ---&gt; some.field.name</li> * <li>someFieldName ---&gt; some.field.name
* <li>_someFieldName ---&gt; _some.field.name</li> * <li>_someFieldName ---&gt; _some.field.name
* <li>aStringField ---&gt; a.string.field</li> * <li>aStringField ---&gt; a.string.field
* <li>aURL ---&gt; a.u.r.l</li> * <li>aURL ---&gt; a.u.r.l
* </ul> * </ul>
*
* Using dots in JavaScript is not recommended since dot is also used for a member sign in * Using dots in JavaScript is not recommended since dot is also used for a member sign in
* expressions. This requires that a field named with dots is always accessed as a quoted * expressions. This requires that a field named with dots is always accessed as a quoted property
* property like {@code myobject['my.field']}. Accessing it as an object field * like {@code myobject['my.field']}. Accessing it as an object field {@code myobject.my.field}
* {@code myobject.my.field} will result in an unintended JavaScript expression. * will result in an unintended JavaScript expression.
* *
* @since 2.8.4 * @since 2.8.4
*/ */
LOWER_CASE_WITH_DOTS() { LOWER_CASE_WITH_DOTS() {
@Override public String translateName(Field f) { @Override
public String translateName(Field f) {
return separateCamelCase(f.getName(), '.').toLowerCase(Locale.ENGLISH); return separateCamelCase(f.getName(), '.').toLowerCase(Locale.ENGLISH);
} }
}; };
/** /**
* Converts the field name that uses camel-case define word separation into * Converts the field name that uses camel-case define word separation into separate words that
* separate words that are separated by the provided {@code separator}. * are separated by the provided {@code separator}.
*/ */
static String separateCamelCase(String name, char separator) { static String separateCamelCase(String name, char separator) {
StringBuilder translation = new StringBuilder(); StringBuilder translation = new StringBuilder();
@ -177,9 +188,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy {
return translation.toString(); return translation.toString();
} }
/** /** Ensures the JSON field names begins with an upper case letter. */
* Ensures the JSON field names begins with an upper case letter.
*/
static String upperCaseFirstLetter(String s) { static String upperCaseFirstLetter(String s) {
int length = s.length(); int length = s.length();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {

View File

@ -20,8 +20,8 @@ import java.lang.reflect.Field;
/** /**
* A mechanism for providing custom field naming in Gson. This allows the client code to translate * A mechanism for providing custom field naming in Gson. This allows the client code to translate
* field names into a particular convention that is not supported as a normal Java field * field names into a particular convention that is not supported as a normal Java field declaration
* declaration rules. For example, Java does not support "-" characters in a field name. * rules. For example, Java does not support "-" characters in a field name.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch

View File

@ -22,8 +22,9 @@ import java.util.Objects;
/** /**
* A class used to control what the serialization output looks like. * A class used to control what the serialization output looks like.
* *
* <p>It currently has the following configuration methods, but more methods * <p>It currently has the following configuration methods, but more methods might be added in the
* might be added in the future: * future:
*
* <ul> * <ul>
* <li>{@link #withNewline(String)} * <li>{@link #withNewline(String)}
* <li>{@link #withIndent(String)} * <li>{@link #withIndent(String)}
@ -33,7 +34,6 @@ import java.util.Objects;
* @see GsonBuilder#setFormattingStyle(FormattingStyle) * @see GsonBuilder#setFormattingStyle(FormattingStyle)
* @see JsonWriter#setFormattingStyle(FormattingStyle) * @see JsonWriter#setFormattingStyle(FormattingStyle)
* @see <a href="https://en.wikipedia.org/wiki/Newline">Wikipedia Newline article</a> * @see <a href="https://en.wikipedia.org/wiki/Newline">Wikipedia Newline article</a>
*
* @since $next-version$ * @since $next-version$
*/ */
public class FormattingStyle { public class FormattingStyle {
@ -43,6 +43,7 @@ public class FormattingStyle {
/** /**
* The default compact formatting style: * The default compact formatting style:
*
* <ul> * <ul>
* <li>no newline * <li>no newline
* <li>no indent * <li>no indent
@ -53,14 +54,14 @@ public class FormattingStyle {
/** /**
* The default pretty printing formatting style: * The default pretty printing formatting style:
*
* <ul> * <ul>
* <li>{@code "\n"} as newline * <li>{@code "\n"} as newline
* <li>two spaces as indent * <li>two spaces as indent
* <li>a space between {@code ':'} and the subsequent value * <li>a space between {@code ':'} and the subsequent value
* </ul> * </ul>
*/ */
public static final FormattingStyle PRETTY = public static final FormattingStyle PRETTY = new FormattingStyle("\n", " ", true);
new FormattingStyle("\n", " ", true);
private FormattingStyle(String newline, String indent, boolean spaceAfterSeparators) { private FormattingStyle(String newline, String indent, boolean spaceAfterSeparators) {
Objects.requireNonNull(newline, "newline == null"); Objects.requireNonNull(newline, "newline == null");
@ -81,11 +82,11 @@ public class FormattingStyle {
/** /**
* Creates a {@link FormattingStyle} with the specified newline setting. * Creates a {@link FormattingStyle} with the specified newline setting.
* *
* <p>It can be used to accommodate certain OS convention, for example * <p>It can be used to accommodate certain OS convention, for example hardcode {@code "\n"} for
* hardcode {@code "\n"} for Linux and macOS, {@code "\r\n"} for Windows, or * Linux and macOS, {@code "\r\n"} for Windows, or call {@link java.lang.System#lineSeparator()}
* call {@link java.lang.System#lineSeparator()} to match the current OS.</p> * to match the current OS.
* *
* <p>Only combinations of {@code \n} and {@code \r} are allowed.</p> * <p>Only combinations of {@code \n} and {@code \r} are allowed.
* *
* @param newline the string value that will be used as newline. * @param newline the string value that will be used as newline.
* @return a newly created {@link FormattingStyle} * @return a newly created {@link FormattingStyle}
@ -97,7 +98,7 @@ public class FormattingStyle {
/** /**
* Creates a {@link FormattingStyle} with the specified indent string. * Creates a {@link FormattingStyle} with the specified indent string.
* *
* <p>Only combinations of spaces and tabs allowed in indent.</p> * <p>Only combinations of spaces and tabs allowed in indent.
* *
* @param indent the string value that will be used as indent. * @param indent the string value that will be used as indent.
* @return a newly created {@link FormattingStyle} * @return a newly created {@link FormattingStyle}
@ -107,12 +108,12 @@ public class FormattingStyle {
} }
/** /**
* Creates a {@link FormattingStyle} which either uses a space after * Creates a {@link FormattingStyle} which either uses a space after the separators {@code ','}
* the separators {@code ','} and {@code ':'} in the JSON output, or not. * and {@code ':'} in the JSON output, or not.
* *
* <p>This setting has no effect on the {@linkplain #withNewline(String) configured newline}. * <p>This setting has no effect on the {@linkplain #withNewline(String) configured newline}. If a
* If a non-empty newline is configured, it will always be added after * non-empty newline is configured, it will always be added after {@code ','} and no space is
* {@code ','} and no space is added after the {@code ','} in that case.</p> * added after the {@code ','} in that case.
* *
* @param spaceAfterSeparators whether to output a space after {@code ','} and {@code ':'}. * @param spaceAfterSeparators whether to output a space after {@code ','} and {@code ':'}.
* @return a newly created {@link FormattingStyle} * @return a newly created {@link FormattingStyle}
@ -139,9 +140,7 @@ public class FormattingStyle {
return this.indent; return this.indent;
} }
/** /** Returns whether a space will be used after {@code ','} and {@code ':'}. */
* Returns whether a space will be used after {@code ','} and {@code ':'}.
*/
public boolean usesSpaceAfterSeparators() { public boolean usesSpaceAfterSeparators() {
return this.spaceAfterSeparators; return this.spaceAfterSeparators;
} }

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,6 @@ import com.google.gson.internal.sql.SqlTypesSupport;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -54,10 +53,10 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
/** /**
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration * Use this builder to construct a {@link Gson} instance when you need to set configuration options
* options other than the default. For {@link Gson} with default configuration, it is simpler to * other than the default. For {@link Gson} with default configuration, it is simpler to use {@code
* use {@code new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its * new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its various
* various configuration methods, and finally calling create.</p> * configuration methods, and finally calling create.
* *
* <p>The following example shows how to use the {@code GsonBuilder} to construct a Gson instance: * <p>The following example shows how to use the {@code GsonBuilder} to construct a Gson instance:
* *
@ -74,15 +73,16 @@ import java.util.Objects;
* </pre> * </pre>
* *
* <p>Notes: * <p>Notes:
*
* <ul> * <ul>
* <li>The order of invocation of configuration methods does not matter.</li> * <li>The order of invocation of configuration methods does not matter.
* <li>The default serialization of {@link Date} and its subclasses in Gson does * <li>The default serialization of {@link Date} and its subclasses in Gson does not contain
* not contain time-zone information. So, if you are using date/time instances, * time-zone information. So, if you are using date/time instances, use {@code GsonBuilder}
* use {@code GsonBuilder} and its {@code setDateFormat} methods.</li> * and its {@code setDateFormat} methods.
* <li>By default no explicit {@link Strictness} is set; some of the {@link Gson} methods * <li>By default no explicit {@link Strictness} is set; some of the {@link Gson} methods behave
* behave as if {@link Strictness#LEGACY_STRICT} was used whereas others behave as * as if {@link Strictness#LEGACY_STRICT} was used whereas others behave as if {@link
* if {@link Strictness#LENIENT} was used. Prefer explicitly setting a strictness * Strictness#LENIENT} was used. Prefer explicitly setting a strictness with {@link
* with {@link #setStrictness(Strictness)} to avoid this legacy behavior. * #setStrictness(Strictness)} to avoid this legacy behavior.
* </ul> * </ul>
* *
* @author Inderjeet Singh * @author Inderjeet Singh
@ -95,8 +95,10 @@ public final class GsonBuilder {
private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY; private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
private final Map<Type, InstanceCreator<?>> instanceCreators = new HashMap<>(); private final Map<Type, InstanceCreator<?>> instanceCreators = new HashMap<>();
private final List<TypeAdapterFactory> factories = new ArrayList<>(); private final List<TypeAdapterFactory> factories = new ArrayList<>();
/** tree-style hierarchy factories. These come after factories for backwards compatibility. */ /** tree-style hierarchy factories. These come after factories for backwards compatibility. */
private final List<TypeAdapterFactory> hierarchyFactories = new ArrayList<>(); private final List<TypeAdapterFactory> hierarchyFactories = new ArrayList<>();
private boolean serializeNulls = DEFAULT_SERIALIZE_NULLS; private boolean serializeNulls = DEFAULT_SERIALIZE_NULLS;
private String datePattern = DEFAULT_DATE_PATTERN; private String datePattern = DEFAULT_DATE_PATTERN;
private int dateStyle = DateFormat.DEFAULT; private int dateStyle = DateFormat.DEFAULT;
@ -114,16 +116,14 @@ public final class GsonBuilder {
/** /**
* Creates a GsonBuilder instance that can be used to build Gson with various configuration * Creates a GsonBuilder instance that can be used to build Gson with various configuration
* settings. GsonBuilder follows the builder pattern, and it is typically used by first * settings. GsonBuilder follows the builder pattern, and it is typically used by first invoking
* invoking various configuration methods to set desired options, and finally calling * various configuration methods to set desired options, and finally calling {@link #create()}.
* {@link #create()}.
*/ */
public GsonBuilder() { public GsonBuilder() {}
}
/** /**
* Constructs a GsonBuilder instance from a Gson instance. The newly constructed GsonBuilder * Constructs a GsonBuilder instance from a Gson instance. The newly constructed GsonBuilder has
* has the same configuration as the previously built Gson instance. * the same configuration as the previously built Gson instance.
* *
* @param gson the gson instance whose configuration should be applied to a new GsonBuilder. * @param gson the gson instance whose configuration should be applied to a new GsonBuilder.
*/ */
@ -151,13 +151,13 @@ public final class GsonBuilder {
} }
/** /**
* Configures Gson to enable versioning support. Versioning support works based on the * Configures Gson to enable versioning support. Versioning support works based on the annotation
* annotation types {@link Since} and {@link Until}. It allows including or excluding fields * types {@link Since} and {@link Until}. It allows including or excluding fields and classes
* and classes based on the specified version. See the documentation of these annotation * based on the specified version. See the documentation of these annotation types for more
* types for more information. * information.
* *
* <p>By default versioning support is disabled and usage of {@code @Since} and {@code @Until} * <p>By default versioning support is disabled and usage of {@code @Since} and {@code @Until} has
* has no effect. * no effect.
* *
* @param version the version number to use. * @param version the version number to use.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
@ -179,13 +179,13 @@ public final class GsonBuilder {
* Gson will exclude all fields marked {@code transient} or {@code static}. This method will * Gson will exclude all fields marked {@code transient} or {@code static}. This method will
* override that behavior. * override that behavior.
* *
* <p>This is a convenience method which behaves as if an {@link ExclusionStrategy} which * <p>This is a convenience method which behaves as if an {@link ExclusionStrategy} which excludes
* excludes these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this builder}. * these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this
* builder}.
* *
* @param modifiers the field modifiers. You must use the modifiers specified in the * @param modifiers the field modifiers. You must use the modifiers specified in the {@link
* {@link java.lang.reflect.Modifier} class. For example, * java.lang.reflect.Modifier} class. For example, {@link
* {@link java.lang.reflect.Modifier#TRANSIENT}, * java.lang.reflect.Modifier#TRANSIENT}, {@link java.lang.reflect.Modifier#STATIC}.
* {@link java.lang.reflect.Modifier#STATIC}.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
@ -197,9 +197,8 @@ public final class GsonBuilder {
/** /**
* Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some * Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some
* special text. This prevents attacks from third-party sites through script sourcing. See * special text. This prevents attacks from third-party sites through script sourcing. See <a
* <a href="http://code.google.com/p/google-gson/issues/detail?id=42">Gson Issue 42</a> * href="http://code.google.com/p/google-gson/issues/detail?id=42">Gson Issue 42</a> for details.
* for details.
* *
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3 * @since 1.3
@ -215,7 +214,8 @@ public final class GsonBuilder {
* that do not have the {@link com.google.gson.annotations.Expose} annotation. * that do not have the {@link com.google.gson.annotations.Expose} annotation.
* *
* <p>This is a convenience method which behaves as if an {@link ExclusionStrategy} which excludes * <p>This is a convenience method which behaves as if an {@link ExclusionStrategy} which excludes
* these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this builder}. * these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this
* builder}.
* *
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/ */
@ -226,8 +226,8 @@ public final class GsonBuilder {
} }
/** /**
* Configure Gson to serialize null fields. By default, Gson omits all fields that are null * Configure Gson to serialize null fields. By default, Gson omits all fields that are null during
* during serialization. * serialization.
* *
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.2 * @since 1.2
@ -239,21 +239,20 @@ public final class GsonBuilder {
} }
/** /**
* Enabling this feature will only change the serialized form if the map key is * Enabling this feature will only change the serialized form if the map key is a complex type
* a complex type (i.e. non-primitive) in its <strong>serialized</strong> JSON * (i.e. non-primitive) in its <strong>serialized</strong> JSON form. The default implementation
* form. The default implementation of map serialization uses {@code toString()} * of map serialization uses {@code toString()} on the key; however, when this is called then one
* on the key; however, when this is called then one of the following cases * of the following cases apply:
* apply:
* *
* <p><b>Maps as JSON objects</b> * <p><b>Maps as JSON objects</b>
* *
* <p>For this case, assume that a type adapter is registered to serialize and * <p>For this case, assume that a type adapter is registered to serialize and deserialize some
* deserialize some {@code Point} class, which contains an x and y coordinate, * {@code Point} class, which contains an x and y coordinate, to/from the JSON Primitive string
* to/from the JSON Primitive string value {@code "(x,y)"}. The Java map would * value {@code "(x,y)"}. The Java map would then be serialized as a {@link JsonObject}.
* then be serialized as a {@link JsonObject}.
* *
* <p>Below is an example: * <p>Below is an example:
* <pre> {@code *
* <pre>{@code
* Gson gson = new GsonBuilder() * Gson gson = new GsonBuilder()
* .register(Point.class, new MyPointTypeAdapter()) * .register(Point.class, new MyPointTypeAdapter())
* .enableComplexMapKeySerialization() * .enableComplexMapKeySerialization()
@ -264,7 +263,10 @@ public final class GsonBuilder {
* original.put(new Point(8, 8), "b"); * original.put(new Point(8, 8), "b");
* System.out.println(gson.toJson(original, type)); * System.out.println(gson.toJson(original, type));
* }</pre> * }</pre>
* The above code prints this JSON object:<pre> {@code *
* The above code prints this JSON object:
*
* <pre>{@code
* { * {
* "(5,6)": "a", * "(5,6)": "a",
* "(8,8)": "b" * "(8,8)": "b"
@ -273,16 +275,16 @@ public final class GsonBuilder {
* *
* <p><b>Maps as JSON arrays</b> * <p><b>Maps as JSON arrays</b>
* *
* <p>For this case, assume that a type adapter was NOT registered for some * <p>For this case, assume that a type adapter was NOT registered for some {@code Point} class,
* {@code Point} class, but rather the default Gson serialization is applied. * but rather the default Gson serialization is applied. In this case, some {@code new Point(2,3)}
* In this case, some {@code new Point(2,3)} would serialize as {@code * would serialize as {@code {"x":2,"y":3}}.
* {"x":2,"y":3}}.
* *
* <p>Given the assumption above, a {@code Map<Point, String>} will be * <p>Given the assumption above, a {@code Map<Point, String>} will be serialized as an array of
* serialized as an array of arrays (can be viewed as an entry set of pairs). * arrays (can be viewed as an entry set of pairs).
* *
* <p>Below is an example of serializing complex types as JSON arrays: * <p>Below is an example of serializing complex types as JSON arrays:
* <pre> {@code *
* <pre>{@code
* Gson gson = new GsonBuilder() * Gson gson = new GsonBuilder()
* .enableComplexMapKeySerialization() * .enableComplexMapKeySerialization()
* .create(); * .create();
@ -291,11 +293,11 @@ public final class GsonBuilder {
* original.put(new Point(5, 6), "a"); * original.put(new Point(5, 6), "a");
* original.put(new Point(8, 8), "b"); * original.put(new Point(8, 8), "b");
* System.out.println(gson.toJson(original, type)); * System.out.println(gson.toJson(original, type));
* } * }</pre>
* </pre>
* *
* The JSON output would look as follows: * The JSON output would look as follows:
* <pre> {@code *
* <pre>{@code
* [ * [
* [ * [
* { * {
@ -324,20 +326,22 @@ public final class GsonBuilder {
} }
/** /**
* Configures Gson to exclude inner classes (= non-{@code static} nested classes) during serialization * Configures Gson to exclude inner classes (= non-{@code static} nested classes) during
* and deserialization. This is a convenience method which behaves as if an {@link ExclusionStrategy} * serialization and deserialization. This is a convenience method which behaves as if an {@link
* which excludes inner classes was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this builder}. * ExclusionStrategy} which excludes inner classes was {@linkplain
* This means inner classes will be serialized as JSON {@code null}, and will be deserialized as * #setExclusionStrategies(ExclusionStrategy...) registered with this builder}. This means inner
* Java {@code null} with their JSON data being ignored. And fields with an inner class as type will * classes will be serialized as JSON {@code null}, and will be deserialized as Java {@code null}
* be ignored during serialization and deserialization. * with their JSON data being ignored. And fields with an inner class as type will be ignored
* during serialization and deserialization.
* *
* <p>By default Gson serializes and deserializes inner classes, but ignores references to the * <p>By default Gson serializes and deserializes inner classes, but ignores references to the
* enclosing instance. Deserialization might not be possible at all when {@link #disableJdkUnsafe()} * enclosing instance. Deserialization might not be possible at all when {@link
* is used (and no custom {@link InstanceCreator} is registered), or it can lead to unexpected * #disableJdkUnsafe()} is used (and no custom {@link InstanceCreator} is registered), or it can
* {@code NullPointerException}s when the deserialized instance is used afterwards. * lead to unexpected {@code NullPointerException}s when the deserialized instance is used
* afterwards.
* *
* <p>In general using inner classes with Gson should be avoided; they should be converted to {@code static} * <p>In general using inner classes with Gson should be avoided; they should be converted to
* nested classes if possible. * {@code static} nested classes if possible.
* *
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3 * @since 1.3
@ -374,12 +378,12 @@ public final class GsonBuilder {
} }
/** /**
* Configures Gson to apply a specific naming strategy to an object's fields during * Configures Gson to apply a specific naming strategy to an object's fields during serialization
* serialization and deserialization. * and deserialization.
* *
* <p>The created Gson instance might only use the field naming strategy once for a * <p>The created Gson instance might only use the field naming strategy once for a field and
* field and cache the result. It is not guaranteed that the strategy will be used * cache the result. It is not guaranteed that the strategy will be used again every time the
* again every time the value of a field is serialized or deserialized. * value of a field is serialized or deserialized.
* *
* @param fieldNamingStrategy the naming strategy to apply to the fields * @param fieldNamingStrategy the naming strategy to apply to the fields
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
@ -421,25 +425,24 @@ public final class GsonBuilder {
/** /**
* Configures Gson to apply a set of exclusion strategies during both serialization and * Configures Gson to apply a set of exclusion strategies during both serialization and
* deserialization. Each of the {@code strategies} will be applied as a disjunction rule. * deserialization. Each of the {@code strategies} will be applied as a disjunction rule. This
* This means that if one of the {@code strategies} suggests that a field (or class) should be * means that if one of the {@code strategies} suggests that a field (or class) should be skipped
* skipped then that field (or object) is skipped during serialization/deserialization. * then that field (or object) is skipped during serialization/deserialization. The strategies are
* The strategies are added to the existing strategies (if any); the existing strategies * added to the existing strategies (if any); the existing strategies are not replaced.
* are not replaced.
* *
* <p>Fields are excluded for serialization and deserialization when * <p>Fields are excluded for serialization and deserialization when {@link
* {@link ExclusionStrategy#shouldSkipField(FieldAttributes) shouldSkipField} returns {@code true}, * ExclusionStrategy#shouldSkipField(FieldAttributes) shouldSkipField} returns {@code true}, or
* or when {@link ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass} returns {@code true} * when {@link ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass} returns {@code true} for
* for the field type. Gson behaves as if the field did not exist; its value is not serialized * the field type. Gson behaves as if the field did not exist; its value is not serialized and on
* and on deserialization if a JSON member with this name exists it is skipped by default.<br> * deserialization if a JSON member with this name exists it is skipped by default.<br>
* When objects of an excluded type (as determined by * When objects of an excluded type (as determined by {@link
* {@link ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass}) are serialized a * ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass}) are serialized a JSON null is
* JSON null is written to output, and when deserialized the JSON value is skipped and * written to output, and when deserialized the JSON value is skipped and {@code null} is
* {@code null} is returned. * returned.
* *
* <p>The created Gson instance might only use an exclusion strategy once for a field or * <p>The created Gson instance might only use an exclusion strategy once for a field or class and
* class and cache the result. It is not guaranteed that the strategy will be used again * cache the result. It is not guaranteed that the strategy will be used again every time the
* every time the value of a field or a class is serialized or deserialized. * value of a field or a class is serialized or deserialized.
* *
* @param strategies the set of strategy object to apply during object (de)serialization. * @param strategies the set of strategy object to apply during object (de)serialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
@ -455,15 +458,14 @@ public final class GsonBuilder {
} }
/** /**
* Configures Gson to apply the passed in exclusion strategy during serialization. * Configures Gson to apply the passed in exclusion strategy during serialization. If this method
* If this method is invoked numerous times with different exclusion strategy objects * is invoked numerous times with different exclusion strategy objects then the exclusion
* then the exclusion strategies that were added will be applied as a disjunction rule. * strategies that were added will be applied as a disjunction rule. This means that if one of the
* This means that if one of the added exclusion strategies suggests that a field (or * added exclusion strategies suggests that a field (or class) should be skipped then that field
* class) should be skipped then that field (or object) is skipped during its * (or object) is skipped during its serialization.
* serialization.
* *
* <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)} * <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)} for a
* for a detailed description of the effect of exclusion strategies. * detailed description of the effect of exclusion strategies.
* *
* @param strategy an exclusion strategy to apply during serialization. * @param strategy an exclusion strategy to apply during serialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
@ -477,15 +479,14 @@ public final class GsonBuilder {
} }
/** /**
* Configures Gson to apply the passed in exclusion strategy during deserialization. * Configures Gson to apply the passed in exclusion strategy during deserialization. If this
* If this method is invoked numerous times with different exclusion strategy objects * method is invoked numerous times with different exclusion strategy objects then the exclusion
* then the exclusion strategies that were added will be applied as a disjunction rule. * strategies that were added will be applied as a disjunction rule. This means that if one of the
* This means that if one of the added exclusion strategies suggests that a field (or * added exclusion strategies suggests that a field (or class) should be skipped then that field
* class) should be skipped then that field (or object) is skipped during its * (or object) is skipped during its deserialization.
* deserialization.
* *
* <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)} * <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)} for a
* for a detailed description of the effect of exclusion strategies. * detailed description of the effect of exclusion strategies.
* *
* @param strategy an exclusion strategy to apply during deserialization. * @param strategy an exclusion strategy to apply during deserialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
@ -513,8 +514,9 @@ public final class GsonBuilder {
} }
/** /**
* Configures Gson to output JSON that uses a certain kind of formatting style (for example newline and indent). * Configures Gson to output JSON that uses a certain kind of formatting style (for example
* This option only affects JSON serialization. By default Gson produces compact JSON output without any formatting. * newline and indent). This option only affects JSON serialization. By default Gson produces
* compact JSON output without any formatting.
* *
* @param formattingStyle the formatting style to use. * @param formattingStyle the formatting style to use.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
@ -529,16 +531,17 @@ public final class GsonBuilder {
/** /**
* Sets the strictness of this builder to {@link Strictness#LENIENT}. * Sets the strictness of this builder to {@link Strictness#LENIENT}.
* *
* @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with * @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with {@link
* {@link Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)} * Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)}
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern. * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
* @see JsonReader#setStrictness(Strictness) * @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness) * @see JsonWriter#setStrictness(Strictness)
* @see #setStrictness(Strictness) * @see #setStrictness(Strictness)
*/ */
@Deprecated @Deprecated
@InlineMe(replacement = "this.setStrictness(Strictness.LENIENT)", imports = "com.google.gson.Strictness") @InlineMe(
replacement = "this.setStrictness(Strictness.LENIENT)",
imports = "com.google.gson.Strictness")
@CanIgnoreReturnValue @CanIgnoreReturnValue
public GsonBuilder setLenient() { public GsonBuilder setLenient() {
return setStrictness(Strictness.LENIENT); return setStrictness(Strictness.LENIENT);
@ -547,10 +550,9 @@ public final class GsonBuilder {
/** /**
* Sets the strictness of this builder to the provided parameter. * Sets the strictness of this builder to the provided parameter.
* *
* <p>This changes how strict the * <p>This changes how strict the <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON
* <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON specification</a> is enforced when parsing or * specification</a> is enforced when parsing or writing JSON. For details on this, refer to
* writing JSON. For details on this, refer to {@link JsonReader#setStrictness(Strictness)} and * {@link JsonReader#setStrictness(Strictness)} and {@link JsonWriter#setStrictness(Strictness)}.
* {@link JsonWriter#setStrictness(Strictness)}.</p>
* *
* @param strictness the new strictness mode. May not be {@code null}. * @param strictness the new strictness mode. May not be {@code null}.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern. * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
@ -583,11 +585,12 @@ public final class GsonBuilder {
* will be used to decide the serialization format. * will be used to decide the serialization format.
* *
* <p>The date format will be used to serialize and deserialize {@link java.util.Date} and in case * <p>The date format will be used to serialize and deserialize {@link java.util.Date} and in case
* the {@code java.sql} module is present, also {@link java.sql.Timestamp} and {@link java.sql.Date}. * the {@code java.sql} module is present, also {@link java.sql.Timestamp} and {@link
* java.sql.Date}.
* *
* <p>Note that this pattern must abide by the convention provided by {@code SimpleDateFormat} * <p>Note that this pattern must abide by the convention provided by {@code SimpleDateFormat}
* class. See the documentation in {@link java.text.SimpleDateFormat} for more information on * class. See the documentation in {@link java.text.SimpleDateFormat} for more information on
* valid date and time patterns.</p> * valid date and time patterns.
* *
* @param pattern the pattern that dates will be serialized/deserialized to/from * @param pattern the pattern that dates will be serialized/deserialized to/from
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
@ -601,13 +604,13 @@ public final class GsonBuilder {
} }
/** /**
* Configures Gson to serialize {@code Date} objects according to the style value provided. * Configures Gson to serialize {@code Date} objects according to the style value provided. You
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last * can call this method or {@link #setDateFormat(String)} multiple times, but only the last
* invocation will be used to decide the serialization format. * invocation will be used to decide the serialization format.
* *
* <p>Note that this style value should be one of the predefined constants in the * <p>Note that this style value should be one of the predefined constants in the {@code
* {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more * DateFormat} class. See the documentation in {@link java.text.DateFormat} for more information
* information on the valid style constants.</p> * on the valid style constants.
* *
* @param style the predefined date style that date objects will be serialized/deserialized * @param style the predefined date style that date objects will be serialized/deserialized
* to/from * to/from
@ -622,13 +625,13 @@ public final class GsonBuilder {
} }
/** /**
* Configures Gson to serialize {@code Date} objects according to the style value provided. * Configures Gson to serialize {@code Date} objects according to the style value provided. You
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last * can call this method or {@link #setDateFormat(String)} multiple times, but only the last
* invocation will be used to decide the serialization format. * invocation will be used to decide the serialization format.
* *
* <p>Note that this style value should be one of the predefined constants in the * <p>Note that this style value should be one of the predefined constants in the {@code
* {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more * DateFormat} class. See the documentation in {@link java.text.DateFormat} for more information
* information on the valid style constants.</p> * on the valid style constants.
* *
* @param dateStyle the predefined date style that date objects will be serialized/deserialized * @param dateStyle the predefined date style that date objects will be serialized/deserialized
* to/from * to/from
@ -655,22 +658,24 @@ public final class GsonBuilder {
* types! For example, applications registering {@code boolean.class} should also register {@code * types! For example, applications registering {@code boolean.class} should also register {@code
* Boolean.class}. * Boolean.class}.
* *
* <p>{@link JsonSerializer} and {@link JsonDeserializer} are made "{@code null}-safe". This * <p>{@link JsonSerializer} and {@link JsonDeserializer} are made "{@code null}-safe". This means
* means when trying to serialize {@code null}, Gson will write a JSON {@code null} and the * when trying to serialize {@code null}, Gson will write a JSON {@code null} and the serializer
* serializer is not called. Similarly when deserializing a JSON {@code null}, Gson will emit * is not called. Similarly when deserializing a JSON {@code null}, Gson will emit {@code null}
* {@code null} without calling the deserializer. If it is desired to handle {@code null} values, * without calling the deserializer. If it is desired to handle {@code null} values, a {@link
* a {@link TypeAdapter} should be used instead. * TypeAdapter} should be used instead.
* *
* @param type the type definition for the type adapter being registered * @param type the type definition for the type adapter being registered
* @param typeAdapter This object must implement at least one of the {@link TypeAdapter}, * @param typeAdapter This object must implement at least one of the {@link TypeAdapter}, {@link
* {@link InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces. * InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @throws IllegalArgumentException if the type adapter being registered is for {@code Object} class or {@link JsonElement} or any of its subclasses * @throws IllegalArgumentException if the type adapter being registered is for {@code Object}
* class or {@link JsonElement} or any of its subclasses
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) { public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
Objects.requireNonNull(type); Objects.requireNonNull(type);
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?> $Gson$Preconditions.checkArgument(
typeAdapter instanceof JsonSerializer<?>
|| typeAdapter instanceof JsonDeserializer<?> || typeAdapter instanceof JsonDeserializer<?>
|| typeAdapter instanceof InstanceCreator<?> || typeAdapter instanceof InstanceCreator<?>
|| typeAdapter instanceof TypeAdapter<?>); || typeAdapter instanceof TypeAdapter<?>);
@ -688,7 +693,8 @@ public final class GsonBuilder {
} }
if (typeAdapter instanceof TypeAdapter<?>) { if (typeAdapter instanceof TypeAdapter<?>) {
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapterFactory factory = TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter); TypeAdapterFactory factory =
TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter) typeAdapter);
factories.add(factory); factories.add(factory);
} }
return this; return this;
@ -696,19 +702,18 @@ public final class GsonBuilder {
private boolean isTypeObjectOrJsonElement(Type type) { private boolean isTypeObjectOrJsonElement(Type type) {
return type instanceof Class return type instanceof Class
&& (type == Object.class && (type == Object.class || JsonElement.class.isAssignableFrom((Class<?>) type));
|| JsonElement.class.isAssignableFrom((Class<?>) type));
} }
/** /**
* Register a factory for type adapters. Registering a factory is useful when the type * Register a factory for type adapters. Registering a factory is useful when the type adapter
* adapter needs to be configured based on the type of the field being processed. Gson * needs to be configured based on the type of the field being processed. Gson is designed to
* is designed to handle a large number of factories, so you should consider registering * handle a large number of factories, so you should consider registering them to be at par with
* them to be at par with registering an individual type adapter. * registering an individual type adapter.
* *
* <p>The created Gson instance might only use the factory once to create an adapter for * <p>The created Gson instance might only use the factory once to create an adapter for a
* a specific type and cache the result. It is not guaranteed that the factory will be used * specific type and cache the result. It is not guaranteed that the factory will be used again
* again every time the type is serialized or deserialized. * every time the type is serialized or deserialized.
* *
* @since 2.1 * @since 2.1
*/ */
@ -721,23 +726,25 @@ public final class GsonBuilder {
/** /**
* Configures Gson for custom serialization or deserialization for an inheritance type hierarchy. * Configures Gson for custom serialization or deserialization for an inheritance type hierarchy.
* This method combines the registration of a {@link TypeAdapter}, {@link JsonSerializer} and * This method combines the registration of a {@link TypeAdapter}, {@link JsonSerializer} and a
* a {@link JsonDeserializer}. If a type adapter was previously registered for the specified * {@link JsonDeserializer}. If a type adapter was previously registered for the specified type
* type hierarchy, it is overridden. If a type adapter is registered for a specific type in * hierarchy, it is overridden. If a type adapter is registered for a specific type in the type
* the type hierarchy, it will be invoked instead of the one registered for the type hierarchy. * hierarchy, it will be invoked instead of the one registered for the type hierarchy.
* *
* @param baseType the class definition for the type adapter being registered for the base class * @param baseType the class definition for the type adapter being registered for the base class
* or interface * or interface
* @param typeAdapter This object must implement at least one of {@link TypeAdapter}, * @param typeAdapter This object must implement at least one of {@link TypeAdapter}, {@link
* {@link JsonSerializer} or {@link JsonDeserializer} interfaces. * JsonSerializer} or {@link JsonDeserializer} interfaces.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @throws IllegalArgumentException if the type adapter being registered is for {@link JsonElement} or any of its subclasses * @throws IllegalArgumentException if the type adapter being registered is for {@link
* JsonElement} or any of its subclasses
* @since 1.7 * @since 1.7
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) { public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) {
Objects.requireNonNull(baseType); Objects.requireNonNull(baseType);
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?> $Gson$Preconditions.checkArgument(
typeAdapter instanceof JsonSerializer<?>
|| typeAdapter instanceof JsonDeserializer<?> || typeAdapter instanceof JsonDeserializer<?>
|| typeAdapter instanceof TypeAdapter<?>); || typeAdapter instanceof TypeAdapter<?>);
@ -750,7 +757,8 @@ public final class GsonBuilder {
} }
if (typeAdapter instanceof TypeAdapter<?>) { if (typeAdapter instanceof TypeAdapter<?>) {
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapterFactory factory = TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter)typeAdapter); TypeAdapterFactory factory =
TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter) typeAdapter);
factories.add(factory); factories.add(factory);
} }
return this; return this;
@ -758,20 +766,19 @@ public final class GsonBuilder {
/** /**
* Section 6 of <a href="https://www.ietf.org/rfc/rfc8259.txt">JSON specification</a> disallows * Section 6 of <a href="https://www.ietf.org/rfc/rfc8259.txt">JSON specification</a> disallows
* special double values (NaN, Infinity, -Infinity). However, * special double values (NaN, Infinity, -Infinity). However, <a
* <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript * href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript
* specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript * specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
* values. Moreover, most JavaScript engines will accept these special values in JSON without * values. Moreover, most JavaScript engines will accept these special values in JSON without
* problem. So, at a practical level, it makes sense to accept these values as valid JSON even * problem. So, at a practical level, it makes sense to accept these values as valid JSON even
* though JSON specification disallows them. * though JSON specification disallows them.
* *
* <p>Gson always accepts these special values during deserialization. However, it outputs * <p>Gson always accepts these special values during deserialization. However, it outputs
* strictly compliant JSON. Hence, if it encounters a float value {@link Float#NaN}, * strictly compliant JSON. Hence, if it encounters a float value {@link Float#NaN}, {@link
* {@link Float#POSITIVE_INFINITY}, {@link Float#NEGATIVE_INFINITY}, or a double value * Float#POSITIVE_INFINITY}, {@link Float#NEGATIVE_INFINITY}, or a double value {@link
* {@link Double#NaN}, {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, it * Double#NaN}, {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, it will throw
* will throw an {@link IllegalArgumentException}. This method provides a way to override the * an {@link IllegalArgumentException}. This method provides a way to override the default
* default behavior when you know that the JSON receiver will be able to handle these special * behavior when you know that the JSON receiver will be able to handle these special values.
* values.
* *
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3 * @since 1.3
@ -785,15 +792,14 @@ public final class GsonBuilder {
/** /**
* Disables usage of JDK's {@code sun.misc.Unsafe}. * Disables usage of JDK's {@code sun.misc.Unsafe}.
* *
* <p>By default Gson uses {@code Unsafe} to create instances of classes which don't have * <p>By default Gson uses {@code Unsafe} to create instances of classes which don't have a
* a no-args constructor. However, {@code Unsafe} might not be available for all Java * no-args constructor. However, {@code Unsafe} might not be available for all Java runtimes. For
* runtimes. For example Android does not provide {@code Unsafe}, or only with limited * example Android does not provide {@code Unsafe}, or only with limited functionality.
* functionality. Additionally {@code Unsafe} creates instances without executing any * Additionally {@code Unsafe} creates instances without executing any constructor or initializer
* constructor or initializer block, or performing initialization of field values. This can * block, or performing initialization of field values. This can lead to surprising and difficult
* lead to surprising and difficult to debug errors. * to debug errors. Therefore, to get reliable behavior regardless of which runtime is used, and
* Therefore, to get reliable behavior regardless of which runtime is used, and to detect * to detect classes which cannot be deserialized in an early stage of development, this method
* classes which cannot be deserialized in an early stage of development, this method allows * allows disabling usage of {@code Unsafe}.
* disabling usage of {@code Unsafe}.
* *
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 2.9.0 * @since 2.9.0
@ -805,20 +811,20 @@ public final class GsonBuilder {
} }
/** /**
* Adds a reflection access filter. A reflection access filter prevents Gson from using * Adds a reflection access filter. A reflection access filter prevents Gson from using reflection
* reflection for the serialization and deserialization of certain classes. The logic in * for the serialization and deserialization of certain classes. The logic in the filter specifies
* the filter specifies which classes those are. * which classes those are.
* *
* <p>Filters will be invoked in reverse registration order, that is, the most recently * <p>Filters will be invoked in reverse registration order, that is, the most recently added
* added filter will be invoked first. * filter will be invoked first.
* *
* <p>By default Gson has no filters configured and will try to use reflection for * <p>By default Gson has no filters configured and will try to use reflection for all classes for
* all classes for which no {@link TypeAdapter} has been registered, and for which no * which no {@link TypeAdapter} has been registered, and for which no built-in Gson {@code
* built-in Gson {@code TypeAdapter} exists. * TypeAdapter} exists.
* *
* <p>The created Gson instance might only use an access filter once for a class or its * <p>The created Gson instance might only use an access filter once for a class or its members
* members and cache the result. It is not guaranteed that the filter will be used again * and cache the result. It is not guaranteed that the filter will be used again every time a
* every time a class or its members are accessed during serialization or deserialization. * class or its members are accessed during serialization or deserialization.
* *
* @param filter filter to add * @param filter filter to add
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
@ -838,7 +844,8 @@ public final class GsonBuilder {
* @return an instance of Gson configured with the options currently set in this builder * @return an instance of Gson configured with the options currently set in this builder
*/ */
public Gson create() { public Gson create() {
List<TypeAdapterFactory> factories = new ArrayList<>(this.factories.size() + this.hierarchyFactories.size() + 3); List<TypeAdapterFactory> factories =
new ArrayList<>(this.factories.size() + this.hierarchyFactories.size() + 3);
factories.addAll(this.factories); factories.addAll(this.factories);
Collections.reverse(factories); Collections.reverse(factories);
@ -848,17 +855,32 @@ public final class GsonBuilder {
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories); addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators), return new Gson(
serializeNulls, complexMapKeySerialization, excluder,
generateNonExecutableJson, escapeHtmlChars, formattingStyle, strictness, fieldNamingPolicy,
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy, new HashMap<>(instanceCreators),
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories), serializeNulls,
new ArrayList<>(this.hierarchyFactories), factories, complexMapKeySerialization,
objectToNumberStrategy, numberToNumberStrategy, new ArrayList<>(reflectionFilters)); generateNonExecutableJson,
escapeHtmlChars,
formattingStyle,
strictness,
serializeSpecialFloatingPointValues,
useJdkUnsafe,
longSerializationPolicy,
datePattern,
dateStyle,
timeStyle,
new ArrayList<>(this.factories),
new ArrayList<>(this.hierarchyFactories),
factories,
objectToNumberStrategy,
numberToNumberStrategy,
new ArrayList<>(reflectionFilters));
} }
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, private void addTypeAdaptersForDate(
List<TypeAdapterFactory> factories) { String datePattern, int dateStyle, int timeStyle, List<TypeAdapterFactory> factories) {
TypeAdapterFactory dateAdapterFactory; TypeAdapterFactory dateAdapterFactory;
boolean sqlTypesSupported = SqlTypesSupport.SUPPORTS_SQL_TYPES; boolean sqlTypesSupported = SqlTypesSupport.SUPPORTS_SQL_TYPES;
TypeAdapterFactory sqlTimestampAdapterFactory = null; TypeAdapterFactory sqlTimestampAdapterFactory = null;
@ -868,15 +890,19 @@ public final class GsonBuilder {
dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(datePattern); dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(datePattern);
if (sqlTypesSupported) { if (sqlTypesSupported) {
sqlTimestampAdapterFactory = SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(datePattern); sqlTimestampAdapterFactory =
SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(datePattern);
sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(datePattern); sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(datePattern);
} }
} else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) { } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(dateStyle, timeStyle); dateAdapterFactory =
DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(dateStyle, timeStyle);
if (sqlTypesSupported) { if (sqlTypesSupported) {
sqlTimestampAdapterFactory = SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle); sqlTimestampAdapterFactory =
sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle); SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle);
sqlDateAdapterFactory =
SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle);
} }
} else { } else {
return; return;

View File

@ -20,14 +20,15 @@ import java.lang.reflect.Type;
/** /**
* This interface is implemented to create instances of a class that does not define a no-args * This interface is implemented to create instances of a class that does not define a no-args
* constructor. If you can modify the class, you should instead add a private, or public * constructor. If you can modify the class, you should instead add a private, or public no-args
* no-args constructor. However, that is not possible for library classes, such as JDK classes, or * constructor. However, that is not possible for library classes, such as JDK classes, or a
* a third-party library that you do not have source-code of. In such cases, you should define an * third-party library that you do not have source-code of. In such cases, you should define an
* instance creator for the class. Implementations of this interface should be registered with * instance creator for the class. Implementations of this interface should be registered with
* {@link GsonBuilder#registerTypeAdapter(Type, Object)} method before Gson will be able to use * {@link GsonBuilder#registerTypeAdapter(Type, Object)} method before Gson will be able to use
* them. * them.
* <p>Let us look at an example where defining an InstanceCreator might be useful. The *
* {@code Id} class defined below does not have a default no-args constructor.</p> * <p>Let us look at an example where defining an InstanceCreator might be useful. The {@code Id}
* class defined below does not have a default no-args constructor.
* *
* <pre> * <pre>
* public class Id&lt;T&gt; { * public class Id&lt;T&gt; {
@ -42,7 +43,7 @@ import java.lang.reflect.Type;
* *
* <p>If Gson encounters an object of type {@code Id} during deserialization, it will throw an * <p>If Gson encounters an object of type {@code Id} during deserialization, it will throw an
* exception. The easiest way to solve this problem will be to add a (public or private) no-args * exception. The easiest way to solve this problem will be to add a (public or private) no-args
* constructor as follows:</p> * constructor as follows:
* *
* <pre> * <pre>
* private Id() { * private Id() {
@ -51,8 +52,8 @@ import java.lang.reflect.Type;
* </pre> * </pre>
* *
* <p>However, let us assume that the developer does not have access to the source-code of the * <p>However, let us assume that the developer does not have access to the source-code of the
* {@code Id} class, or does not want to define a no-args constructor for it. The developer * {@code Id} class, or does not want to define a no-args constructor for it. The developer can
* can solve this problem by defining an {@code InstanceCreator} for {@code Id}:</p> * solve this problem by defining an {@code InstanceCreator} for {@code Id}:
* *
* <pre> * <pre>
* class IdInstanceCreator implements InstanceCreator&lt;Id&gt; { * class IdInstanceCreator implements InstanceCreator&lt;Id&gt; {
@ -64,17 +65,15 @@ import java.lang.reflect.Type;
* *
* <p>Note that it does not matter what the fields of the created instance contain since Gson will * <p>Note that it does not matter what the fields of the created instance contain since Gson will
* overwrite them with the deserialized values specified in JSON. You should also ensure that a * overwrite them with the deserialized values specified in JSON. You should also ensure that a
* <i>new</i> object is returned, not a common object since its fields will be overwritten. * <i>new</i> object is returned, not a common object since its fields will be overwritten. The
* The developer will need to register {@code IdInstanceCreator} with Gson as follows:</p> * developer will need to register {@code IdInstanceCreator} with Gson as follows:
* *
* <pre> * <pre>
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdInstanceCreator()).create(); * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdInstanceCreator()).create();
* </pre> * </pre>
* *
* @param <T> the type of object that will be created by this implementation. * @param <T> the type of object that will be created by this implementation.
*
* @see GsonBuilder#registerTypeAdapter(Type, Object) * @see GsonBuilder#registerTypeAdapter(Type, Object)
*
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*/ */
@ -82,10 +81,10 @@ public interface InstanceCreator<T> {
/** /**
* Gson invokes this call-back method during deserialization to create an instance of the * Gson invokes this call-back method during deserialization to create an instance of the
* specified type. The fields of the returned instance are overwritten with the data present * specified type. The fields of the returned instance are overwritten with the data present in
* in the JSON. Since the prior contents of the object are destroyed and overwritten, do not * the JSON. Since the prior contents of the object are destroyed and overwritten, do not return
* return an instance that is useful elsewhere. In particular, do not return a common instance, * an instance that is useful elsewhere. In particular, do not return a common instance, always
* always use {@code new} to create a new instance. * use {@code new} to create a new instance.
* *
* @param type the parameterized T represented as a {@link Type}. * @param type the parameterized T represented as a {@link Type}.
* @return a default object instance of type T. * @return a default object instance of type T.

View File

@ -39,9 +39,7 @@ import java.util.List;
public final class JsonArray extends JsonElement implements Iterable<JsonElement> { public final class JsonArray extends JsonElement implements Iterable<JsonElement> {
private final ArrayList<JsonElement> elements; private final ArrayList<JsonElement> elements;
/** /** Creates an empty JsonArray. */
* Creates an empty JsonArray.
*/
@SuppressWarnings("deprecation") // superclass constructor @SuppressWarnings("deprecation") // superclass constructor
public JsonArray() { public JsonArray() {
elements = new ArrayList<>(); elements = new ArrayList<>();
@ -51,8 +49,7 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
* Creates an empty JsonArray with the desired initial capacity. * Creates an empty JsonArray with the desired initial capacity.
* *
* @param capacity initial capacity. * @param capacity initial capacity.
* @throws IllegalArgumentException if the {@code capacity} is * @throws IllegalArgumentException if the {@code capacity} is negative
* negative
* @since 2.8.1 * @since 2.8.1
*/ */
@SuppressWarnings("deprecation") // superclass constructor @SuppressWarnings("deprecation") // superclass constructor
@ -152,8 +149,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Removes the first occurrence of the specified element from this array, if it is present. * Removes the first occurrence of the specified element from this array, if it is present. If the
* If the array does not contain the element, it is unchanged. * array does not contain the element, it is unchanged.
* *
* @param element element to be removed from this array, if present * @param element element to be removed from this array, if present
* @return true if this array contained the specified element, false otherwise * @return true if this array contained the specified element, false otherwise
@ -165,9 +162,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Removes the element at the specified position in this array. Shifts any subsequent elements * Removes the element at the specified position in this array. Shifts any subsequent elements to
* to the left (subtracts one from their indices). Returns the element that was removed from * the left (subtracts one from their indices). Returns the element removed from the array.
* the array.
* *
* @param index index the index of the element to be removed * @param index index the index of the element to be removed
* @return the element previously at the specified position * @return the element previously at the specified position
@ -241,9 +237,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a {@link Number} if it contains a single element. * Convenience method to get this array as a {@link Number} if it contains a single element. This
* This method calls {@link JsonElement#getAsNumber()} on the element, therefore any * method calls {@link JsonElement#getAsNumber()} on the element, therefore any of the exceptions
* of the exceptions declared by that method can occur. * declared by that method can occur.
* *
* @return this element as a number if it is single element array. * @return this element as a number if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -254,9 +250,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a {@link String} if it contains a single element. * Convenience method to get this array as a {@link String} if it contains a single element. This
* This method calls {@link JsonElement#getAsString()} on the element, therefore any * method calls {@link JsonElement#getAsString()} on the element, therefore any of the exceptions
* of the exceptions declared by that method can occur. * declared by that method can occur.
* *
* @return this element as a String if it is single element array. * @return this element as a String if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -267,9 +263,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a double if it contains a single element. * Convenience method to get this array as a double if it contains a single element. This method
* This method calls {@link JsonElement#getAsDouble()} on the element, therefore any * calls {@link JsonElement#getAsDouble()} on the element, therefore any of the exceptions
* of the exceptions declared by that method can occur. * declared by that method can occur.
* *
* @return this element as a double if it is single element array. * @return this element as a double if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -281,8 +277,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
/** /**
* Convenience method to get this array as a {@link BigDecimal} if it contains a single element. * Convenience method to get this array as a {@link BigDecimal} if it contains a single element.
* This method calls {@link JsonElement#getAsBigDecimal()} on the element, therefore any * This method calls {@link JsonElement#getAsBigDecimal()} on the element, therefore any of the
* of the exceptions declared by that method can occur. * exceptions declared by that method can occur.
* *
* @return this element as a {@link BigDecimal} if it is single element array. * @return this element as a {@link BigDecimal} if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -295,8 +291,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
/** /**
* Convenience method to get this array as a {@link BigInteger} if it contains a single element. * Convenience method to get this array as a {@link BigInteger} if it contains a single element.
* This method calls {@link JsonElement#getAsBigInteger()} on the element, therefore any * This method calls {@link JsonElement#getAsBigInteger()} on the element, therefore any of the
* of the exceptions declared by that method can occur. * exceptions declared by that method can occur.
* *
* @return this element as a {@link BigInteger} if it is single element array. * @return this element as a {@link BigInteger} if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -308,9 +304,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a float if it contains a single element. * Convenience method to get this array as a float if it contains a single element. This method
* This method calls {@link JsonElement#getAsFloat()} on the element, therefore any * calls {@link JsonElement#getAsFloat()} on the element, therefore any of the exceptions declared
* of the exceptions declared by that method can occur. * by that method can occur.
* *
* @return this element as a float if it is single element array. * @return this element as a float if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -321,9 +317,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a long if it contains a single element. * Convenience method to get this array as a long if it contains a single element. This method
* This method calls {@link JsonElement#getAsLong()} on the element, therefore any * calls {@link JsonElement#getAsLong()} on the element, therefore any of the exceptions declared
* of the exceptions declared by that method can occur. * by that method can occur.
* *
* @return this element as a long if it is single element array. * @return this element as a long if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -334,9 +330,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as an integer if it contains a single element. * Convenience method to get this array as an integer if it contains a single element. This method
* This method calls {@link JsonElement#getAsInt()} on the element, therefore any * calls {@link JsonElement#getAsInt()} on the element, therefore any of the exceptions declared
* of the exceptions declared by that method can occur. * by that method can occur.
* *
* @return this element as an integer if it is single element array. * @return this element as an integer if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -347,9 +343,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a primitive byte if it contains a single element. * Convenience method to get this array as a primitive byte if it contains a single element. This
* This method calls {@link JsonElement#getAsByte()} on the element, therefore any * method calls {@link JsonElement#getAsByte()} on the element, therefore any of the exceptions
* of the exceptions declared by that method can occur. * declared by that method can occur.
* *
* @return this element as a primitive byte if it is single element array. * @return this element as a primitive byte if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -360,9 +356,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a character if it contains a single element. * Convenience method to get this array as a character if it contains a single element. This
* This method calls {@link JsonElement#getAsCharacter()} on the element, therefore any * method calls {@link JsonElement#getAsCharacter()} on the element, therefore any of the
* of the exceptions declared by that method can occur. * exceptions declared by that method can occur.
* *
* @return this element as a primitive short if it is single element array. * @return this element as a primitive short if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -376,9 +372,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a primitive short if it contains a single element. * Convenience method to get this array as a primitive short if it contains a single element. This
* This method calls {@link JsonElement#getAsShort()} on the element, therefore any * method calls {@link JsonElement#getAsShort()} on the element, therefore any of the exceptions
* of the exceptions declared by that method can occur. * declared by that method can occur.
* *
* @return this element as a primitive short if it is single element array. * @return this element as a primitive short if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -389,9 +385,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Convenience method to get this array as a boolean if it contains a single element. * Convenience method to get this array as a boolean if it contains a single element. This method
* This method calls {@link JsonElement#getAsBoolean()} on the element, therefore any * calls {@link JsonElement#getAsBoolean()} on the element, therefore any of the exceptions
* of the exceptions declared by that method can occur. * declared by that method can occur.
* *
* @return this element as a boolean if it is single element array. * @return this element as a boolean if it is single element array.
* @throws IllegalStateException if the array is empty or has more than one element. * @throws IllegalStateException if the array is empty or has more than one element.
@ -402,12 +398,12 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Returns a mutable {@link List} view of this {@code JsonArray}. Changes to the {@code List} * Returns a mutable {@link List} view of this {@code JsonArray}. Changes to the {@code List} are
* are visible in this {@code JsonArray} and the other way around. * visible in this {@code JsonArray} and the other way around.
* *
* <p>The {@code List} does not permit {@code null} elements. Unlike {@code JsonArray}'s * <p>The {@code List} does not permit {@code null} elements. Unlike {@code JsonArray}'s {@code
* {@code null} handling, a {@link NullPointerException} is thrown when trying to add {@code null}. * null} handling, a {@link NullPointerException} is thrown when trying to add {@code null}. Use
* Use {@link JsonNull} for JSON null values. * {@link JsonNull} for JSON null values.
* *
* @return mutable {@code List} view * @return mutable {@code List} view
* @since 2.10 * @since 2.10
@ -417,9 +413,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Returns whether the other object is equal to this. This method only considers * Returns whether the other object is equal to this. This method only considers the other object
* the other object to be equal if it is an instance of {@code JsonArray} and has * to be equal if it is an instance of {@code JsonArray} and has equal elements in the same order.
* equal elements in the same order.
*/ */
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
@ -427,8 +422,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} }
/** /**
* Returns the hash code of this array. This method calculates the hash code based * Returns the hash code of this array. This method calculates the hash code based on the elements
* on the elements of this array. * of this array.
*/ */
@Override @Override
public int hashCode() { public int hashCode() {

View File

@ -20,8 +20,7 @@ import java.lang.reflect.Type;
/** /**
* Context for deserialization that is passed to a custom deserializer during invocation of its * Context for deserialization that is passed to a custom deserializer during invocation of its
* {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} * {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} method.
* method.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -29,10 +28,10 @@ import java.lang.reflect.Type;
public interface JsonDeserializationContext { public interface JsonDeserializationContext {
/** /**
* Invokes default deserialization on the specified object. It should never be invoked on * Invokes default deserialization on the specified object. It should never be invoked on the
* the element received as a parameter of the * element received as a parameter of the {@link JsonDeserializer#deserialize(JsonElement, Type,
* {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} method. Doing * JsonDeserializationContext)} method. Doing so will result in an infinite loop since Gson will
* so will result in an infinite loop since Gson will in-turn call the custom deserializer again. * in-turn call the custom deserializer again.
* *
* @param json the parse tree. * @param json the parse tree.
* @param typeOfT type of the expected return value. * @param typeOfT type of the expected return value.

View File

@ -19,13 +19,12 @@ package com.google.gson;
import java.lang.reflect.Type; import java.lang.reflect.Type;
/** /**
* <p>Interface representing a custom deserializer for JSON. You should write a custom * Interface representing a custom deserializer for JSON. You should write a custom deserializer, if
* deserializer, if you are not happy with the default deserialization done by Gson. You will * you are not happy with the default deserialization done by Gson. You will also need to register
* also need to register this deserializer through * this deserializer through {@link GsonBuilder#registerTypeAdapter(Type, Object)}.
* {@link GsonBuilder#registerTypeAdapter(Type, Object)}.</p>
* *
* <p>Let us look at example where defining a deserializer will be useful. The {@code Id} class * <p>Let us look at example where defining a deserializer will be useful. The {@code Id} class
* defined below has two fields: {@code clazz} and {@code value}.</p> * defined below has two fields: {@code clazz} and {@code value}.
* *
* <pre> * <pre>
* public class Id&lt;T&gt; { * public class Id&lt;T&gt; {
@ -41,11 +40,11 @@ import java.lang.reflect.Type;
* } * }
* </pre> * </pre>
* *
* <p>The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the * <p>The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the JSON
* JSON string to be <code>{"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you already know * string to be <code>{"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you already know the
* the type of the field that the {@code Id} will be deserialized into, and hence just want to * type of the field that the {@code Id} will be deserialized into, and hence just want to
* deserialize it from a JSON string {@code 20}. You can achieve that by writing a custom * deserialize it from a JSON string {@code 20}. You can achieve that by writing a custom
* deserializer:</p> * deserializer:
* *
* <pre> * <pre>
* class IdDeserializer implements JsonDeserializer&lt;Id&gt; { * class IdDeserializer implements JsonDeserializer&lt;Id&gt; {
@ -57,21 +56,20 @@ import java.lang.reflect.Type;
* } * }
* </pre> * </pre>
* *
* <p>You will also need to register {@code IdDeserializer} with Gson as follows:</p> * <p>You will also need to register {@code IdDeserializer} with Gson as follows:
* *
* <pre> * <pre>
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create(); * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create();
* </pre> * </pre>
* *
* <p>Deserializers should be stateless and thread-safe, otherwise the thread-safety * <p>Deserializers should be stateless and thread-safe, otherwise the thread-safety guarantees of
* guarantees of {@link Gson} might not apply. * {@link Gson} might not apply.
* *
* <p>New applications should prefer {@link TypeAdapter}, whose streaming API * <p>New applications should prefer {@link TypeAdapter}, whose streaming API is more efficient than
* is more efficient than this interface's tree API. * this interface's tree API.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*
* @param <T> type for which the deserializer is being registered. It is possible that a * @param <T> type for which the deserializer is being registered. It is possible that a
* deserializer may be asked to deserialize a specific generic type of the T. * deserializer may be asked to deserialize a specific generic type of the T.
*/ */
@ -80,11 +78,12 @@ public interface JsonDeserializer<T> {
/** /**
* Gson invokes this call-back method during deserialization when it encounters a field of the * Gson invokes this call-back method during deserialization when it encounters a field of the
* specified type. * specified type.
* <p>In the implementation of this call-back method, you should consider invoking *
* {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects * <p>In the implementation of this call-back method, you should consider invoking {@link
* for any non-trivial field of the returned object. However, you should never invoke it on the * JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects for any
* same type passing {@code json} since that will cause an infinite loop (Gson will call your * non-trivial field of the returned object. However, you should never invoke it on the same type
* call-back method again). * passing {@code json} since that will cause an infinite loop (Gson will call your call-back
* method again).
* *
* @param json The Json data being deserialized * @param json The Json data being deserialized
* @param typeOfT The type of the Object to deserialize to * @param typeOfT The type of the Object to deserialize to

View File

@ -25,25 +25,24 @@ import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
/** /**
* A class representing an element of JSON. It could either be a {@link JsonObject}, a * A class representing an element of JSON. It could either be a {@link JsonObject}, a {@link
* {@link JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}. * JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*/ */
public abstract class JsonElement { public abstract class JsonElement {
/** /**
* @deprecated Creating custom {@code JsonElement} subclasses is highly discouraged * @deprecated Creating custom {@code JsonElement} subclasses is highly discouraged and can lead
* and can lead to undefined behavior.<br> * to undefined behavior.<br>
* This constructor is only kept for backward compatibility. * This constructor is only kept for backward compatibility.
*/ */
@Deprecated @Deprecated
public JsonElement() { public JsonElement() {}
}
/** /**
* Returns a deep copy of this element. Immutable elements like primitives * Returns a deep copy of this element. Immutable elements like primitives and nulls are not
* and nulls are not copied. * copied.
* *
* @since 2.8.2 * @since 2.8.2
*/ */
@ -103,10 +102,9 @@ public abstract class JsonElement {
} }
/** /**
* Convenience method to get this element as a {@link JsonArray}. If this element is of some * Convenience method to get this element as a {@link JsonArray}. If this element is of some other
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method * type, an {@link IllegalStateException} will result. Hence it is best to use this method after
* after ensuring that this element is of the desired type by calling {@link #isJsonArray()} * ensuring that this element is of the desired type by calling {@link #isJsonArray()} first.
* first.
* *
* @return this element as a {@link JsonArray}. * @return this element as a {@link JsonArray}.
* @throws IllegalStateException if this element is of another type. * @throws IllegalStateException if this element is of another type.
@ -135,10 +133,9 @@ public abstract class JsonElement {
} }
/** /**
* Convenience method to get this element as a {@link JsonNull}. If this element is of some * Convenience method to get this element as a {@link JsonNull}. If this element is of some other
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method * type, an {@link IllegalStateException} will result. Hence it is best to use this method after
* after ensuring that this element is of the desired type by calling {@link #isJsonNull()} * ensuring that this element is of the desired type by calling {@link #isJsonNull()} first.
* first.
* *
* @return this element as a {@link JsonNull}. * @return this element as a {@link JsonNull}.
* @throws IllegalStateException if this element is of another type. * @throws IllegalStateException if this element is of another type.
@ -156,7 +153,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a boolean value. * Convenience method to get this element as a boolean value.
* *
* @return this element as a primitive boolean value. * @return this element as a primitive boolean value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
*/ */
@ -168,8 +166,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a {@link Number}. * Convenience method to get this element as a {@link Number}.
* *
* @return this element as a {@link Number}. * @return this element as a {@link Number}.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}, * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* or cannot be converted to a number. * JsonArray}, or cannot be converted to a number.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
*/ */
@ -181,7 +179,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a string value. * Convenience method to get this element as a string value.
* *
* @return this element as a string value. * @return this element as a string value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
*/ */
@ -193,7 +192,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a primitive double value. * Convenience method to get this element as a primitive double value.
* *
* @return this element as a primitive double value. * @return this element as a primitive double value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws NumberFormatException if the value contained is not a valid double. * @throws NumberFormatException if the value contained is not a valid double.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
@ -206,7 +206,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a primitive float value. * Convenience method to get this element as a primitive float value.
* *
* @return this element as a primitive float value. * @return this element as a primitive float value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws NumberFormatException if the value contained is not a valid float. * @throws NumberFormatException if the value contained is not a valid float.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
@ -219,7 +220,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a primitive long value. * Convenience method to get this element as a primitive long value.
* *
* @return this element as a primitive long value. * @return this element as a primitive long value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws NumberFormatException if the value contained is not a valid long. * @throws NumberFormatException if the value contained is not a valid long.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
@ -232,7 +234,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a primitive integer value. * Convenience method to get this element as a primitive integer value.
* *
* @return this element as a primitive integer value. * @return this element as a primitive integer value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws NumberFormatException if the value contained is not a valid integer. * @throws NumberFormatException if the value contained is not a valid integer.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
@ -245,7 +248,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a primitive byte value. * Convenience method to get this element as a primitive byte value.
* *
* @return this element as a primitive byte value. * @return this element as a primitive byte value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws NumberFormatException if the value contained is not a valid byte. * @throws NumberFormatException if the value contained is not a valid byte.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
@ -259,8 +263,8 @@ public abstract class JsonElement {
* Convenience method to get the first character of the string value of this element. * Convenience method to get the first character of the string value of this element.
* *
* @return the first character of the string value. * @return the first character of the string value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}, * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* or if its string value is empty. * JsonArray}, or if its string value is empty.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
* @since 1.3 * @since 1.3
@ -276,7 +280,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a {@link BigDecimal}. * Convenience method to get this element as a {@link BigDecimal}.
* *
* @return this element as a {@link BigDecimal}. * @return this element as a {@link BigDecimal}.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws NumberFormatException if this element is not a valid {@link BigDecimal}. * @throws NumberFormatException if this element is not a valid {@link BigDecimal}.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
@ -290,7 +295,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a {@link BigInteger}. * Convenience method to get this element as a {@link BigInteger}.
* *
* @return this element as a {@link BigInteger}. * @return this element as a {@link BigInteger}.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws NumberFormatException if this element is not a valid {@link BigInteger}. * @throws NumberFormatException if this element is not a valid {@link BigInteger}.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
@ -304,7 +310,8 @@ public abstract class JsonElement {
* Convenience method to get this element as a primitive short value. * Convenience method to get this element as a primitive short value.
* *
* @return this element as a primitive short value. * @return this element as a primitive short value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}. * @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
* JsonArray}.
* @throws NumberFormatException if the value contained is not a valid short. * @throws NumberFormatException if the value contained is not a valid short.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains * @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element. * more than a single element.
@ -313,15 +320,14 @@ public abstract class JsonElement {
throw new UnsupportedOperationException(getClass().getSimpleName()); throw new UnsupportedOperationException(getClass().getSimpleName());
} }
/** /** Returns a String representation of this element. */
* Returns a String representation of this element.
*/
@Override @Override
public String toString() { public String toString() {
try { try {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
// Make writer lenient because toString() must not fail, even if for example JsonPrimitive contains NaN // Make writer lenient because toString() must not fail, even if for example JsonPrimitive
// contains NaN
jsonWriter.setStrictness(Strictness.LENIENT); jsonWriter.setStrictness(Strictness.LENIENT);
Streams.write(this, jsonWriter); Streams.write(this, jsonWriter);
return stringWriter.toString(); return stringWriter.toString();

View File

@ -16,8 +16,7 @@
package com.google.gson; package com.google.gson;
/** /**
* This exception is raised when Gson was unable to read an input stream * This exception is raised when Gson was unable to read an input stream or write to one.
* or write to one.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -34,8 +33,8 @@ public final class JsonIOException extends JsonParseException {
} }
/** /**
* Creates exception with the specified cause. Consider using * Creates exception with the specified cause. Consider using {@link #JsonIOException(String,
* {@link #JsonIOException(String, Throwable)} instead if you can describe what happened. * Throwable)} instead if you can describe what happened.
* *
* @param cause root exception that caused this exception to be thrown. * @param cause root exception that caused this exception to be thrown.
*/ */

View File

@ -51,17 +51,13 @@ public final class JsonNull extends JsonElement {
return INSTANCE; return INSTANCE;
} }
/** /** All instances of {@code JsonNull} have the same hash code since they are indistinguishable. */
* All instances of {@code JsonNull} have the same hash code since they are indistinguishable.
*/
@Override @Override
public int hashCode() { public int hashCode() {
return JsonNull.class.hashCode(); return JsonNull.class.hashCode();
} }
/** /** All instances of {@code JsonNull} are considered equal. */
* All instances of {@code JsonNull} are considered equal.
*/
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
return other instanceof JsonNull; return other instanceof JsonNull;

View File

@ -25,11 +25,11 @@ import java.util.Set;
* A class representing an object type in Json. An object consists of name-value pairs where names * A class representing an object type in Json. An object consists of name-value pairs where names
* are strings, and values are any other type of {@link JsonElement}. This allows for a creating a * are strings, and values are any other type of {@link JsonElement}. This allows for a creating a
* tree of JsonElements. The member elements of this object are maintained in order they were added. * tree of JsonElements. The member elements of this object are maintained in order they were added.
* This class does not support {@code null} values. If {@code null} is provided as value argument * This class does not support {@code null} values. If {@code null} is provided as value argument to
* to any of the methods, it is converted to a {@link JsonNull}. * any of the methods, it is converted to a {@link JsonNull}.
* *
* <p>{@code JsonObject} does not implement the {@link Map} interface, but a {@code Map} view * <p>{@code JsonObject} does not implement the {@link Map} interface, but a {@code Map} view of it
* of it can be obtained with {@link #asMap()}. * can be obtained with {@link #asMap()}.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -37,12 +37,9 @@ import java.util.Set;
public final class JsonObject extends JsonElement { public final class JsonObject extends JsonElement {
private final LinkedTreeMap<String, JsonElement> members = new LinkedTreeMap<>(false); private final LinkedTreeMap<String, JsonElement> members = new LinkedTreeMap<>(false);
/** /** Creates an empty JsonObject. */
* Creates an empty JsonObject.
*/
@SuppressWarnings("deprecation") // superclass constructor @SuppressWarnings("deprecation") // superclass constructor
public JsonObject() { public JsonObject() {}
}
/** /**
* Creates a deep copy of this element and all its children. * Creates a deep copy of this element and all its children.
@ -60,8 +57,8 @@ public final class JsonObject extends JsonElement {
/** /**
* Adds a member, which is a name-value pair, to self. The name must be a String, but the value * Adds a member, which is a name-value pair, to self. The name must be a String, but the value
* can be an arbitrary {@link JsonElement}, thereby allowing you to build a full tree of JsonElements * can be an arbitrary {@link JsonElement}, thereby allowing you to build a full tree of
* rooted at this node. * JsonElements rooted at this node.
* *
* @param property name of the member. * @param property name of the member.
* @param value the member object. * @param value the member object.
@ -74,8 +71,8 @@ public final class JsonObject extends JsonElement {
* Removes the {@code property} from this object. * Removes the {@code property} from this object.
* *
* @param property name of the member that should be removed. * @param property name of the member that should be removed.
* @return the {@link JsonElement} object that is being removed, or {@code null} if no * @return the {@link JsonElement} object that is being removed, or {@code null} if no member with
* member with this name exists. * this name exists.
* @since 1.3 * @since 1.3
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
@ -84,8 +81,8 @@ public final class JsonObject extends JsonElement {
} }
/** /**
* Convenience method to add a string member. The specified value is converted to a * Convenience method to add a string member. The specified value is converted to a {@link
* {@link JsonPrimitive} of String. * JsonPrimitive} of String.
* *
* @param property name of the member. * @param property name of the member.
* @param value the string value associated with the member. * @param value the string value associated with the member.
@ -95,8 +92,8 @@ public final class JsonObject extends JsonElement {
} }
/** /**
* Convenience method to add a number member. The specified value is converted to a * Convenience method to add a number member. The specified value is converted to a {@link
* {@link JsonPrimitive} of Number. * JsonPrimitive} of Number.
* *
* @param property name of the member. * @param property name of the member.
* @param value the number value associated with the member. * @param value the number value associated with the member.
@ -106,8 +103,8 @@ public final class JsonObject extends JsonElement {
} }
/** /**
* Convenience method to add a boolean member. The specified value is converted to a * Convenience method to add a boolean member. The specified value is converted to a {@link
* {@link JsonPrimitive} of Boolean. * JsonPrimitive} of Boolean.
* *
* @param property name of the member. * @param property name of the member.
* @param value the boolean value associated with the member. * @param value the boolean value associated with the member.
@ -117,8 +114,8 @@ public final class JsonObject extends JsonElement {
} }
/** /**
* Convenience method to add a char member. The specified value is converted to a * Convenience method to add a char member. The specified value is converted to a {@link
* {@link JsonPrimitive} of Character. * JsonPrimitive} of Character.
* *
* @param property name of the member. * @param property name of the member.
* @param value the char value associated with the member. * @param value the char value associated with the member.
@ -224,12 +221,12 @@ public final class JsonObject extends JsonElement {
} }
/** /**
* Returns a mutable {@link Map} view of this {@code JsonObject}. Changes to the {@code Map} * Returns a mutable {@link Map} view of this {@code JsonObject}. Changes to the {@code Map} are
* are visible in this {@code JsonObject} and the other way around. * visible in this {@code JsonObject} and the other way around.
* *
* <p>The {@code Map} does not permit {@code null} keys or values. Unlike {@code JsonObject}'s * <p>The {@code Map} does not permit {@code null} keys or values. Unlike {@code JsonObject}'s
* {@code null} handling, a {@link NullPointerException} is thrown when trying to add {@code null}. * {@code null} handling, a {@link NullPointerException} is thrown when trying to add {@code
* Use {@link JsonNull} for JSON null values. * null}. Use {@link JsonNull} for JSON null values.
* *
* @return mutable {@code Map} view * @return mutable {@code Map} view
* @since 2.10 * @since 2.10
@ -240,19 +237,17 @@ public final class JsonObject extends JsonElement {
} }
/** /**
* Returns whether the other object is equal to this. This method only considers * Returns whether the other object is equal to this. This method only considers the other object
* the other object to be equal if it is an instance of {@code JsonObject} and has * to be equal if it is an instance of {@code JsonObject} and has equal members, ignoring order.
* equal members, ignoring order.
*/ */
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
return (o == this) || (o instanceof JsonObject return (o == this) || (o instanceof JsonObject && ((JsonObject) o).members.equals(members));
&& ((JsonObject) o).members.equals(members));
} }
/** /**
* Returns the hash code of this object. This method calculates the hash code based * Returns the hash code of this object. This method calculates the hash code based on the members
* on the members of this object, ignoring order. * of this object, ignoring order.
*/ */
@Override @Override
public int hashCode() { public int hashCode() {

View File

@ -17,14 +17,14 @@
package com.google.gson; package com.google.gson;
/** /**
* This exception is raised if there is a serious issue that occurs during parsing of a Json * This exception is raised if there is a serious issue that occurs during parsing of a Json string.
* string. One of the main usages for this class is for the Gson infrastructure. If the incoming * One of the main usages for this class is for the Gson infrastructure. If the incoming Json is
* Json is bad/malicious, an instance of this exception is raised. * bad/malicious, an instance of this exception is raised.
* *
* <p>This exception is a {@link RuntimeException} because it is exposed to the client. Using a * <p>This exception is a {@link RuntimeException} because it is exposed to the client. Using a
* {@link RuntimeException} avoids bad coding practices on the client side where they catch the * {@link RuntimeException} avoids bad coding practices on the client side where they catch the
* exception and do nothing. It is often the case that you want to blow up if there is a parsing * exception and do nothing. It is often the case that you want to blow up if there is a parsing
* error (i.e. often clients do not know how to recover from a {@link JsonParseException}.</p> * error (i.e. often clients do not know how to recover from a {@link JsonParseException}.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -53,8 +53,8 @@ public class JsonParseException extends RuntimeException {
} }
/** /**
* Creates exception with the specified cause. Consider using * Creates exception with the specified cause. Consider using {@link #JsonParseException(String,
* {@link #JsonParseException(String, Throwable)} instead if you can describe what happened. * Throwable)} instead if you can describe what happened.
* *
* @param cause root exception that caused this exception to be thrown. * @param cause root exception that caused this exception to be thrown.
*/ */

View File

@ -32,14 +32,15 @@ import java.io.StringReader;
* @since 1.3 * @since 1.3
*/ */
public final class JsonParser { public final class JsonParser {
/** @deprecated No need to instantiate this class, use the static methods instead. */ /**
* @deprecated No need to instantiate this class, use the static methods instead.
*/
@Deprecated @Deprecated
public JsonParser() {} public JsonParser() {}
/** /**
* Parses the specified JSON string into a parse tree. * Parses the specified JSON string into a parse tree. An exception is thrown if the JSON string
* An exception is thrown if the JSON string has multiple top-level JSON elements, * has multiple top-level JSON elements, or if there is trailing data.
* or if there is trailing data.
* *
* <p>The JSON string is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}. * <p>The JSON string is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
* *
@ -53,16 +54,15 @@ public final class JsonParser {
} }
/** /**
* Parses the complete JSON string provided by the reader into a parse tree. * Parses the complete JSON string provided by the reader into a parse tree. An exception is
* An exception is thrown if the JSON string has multiple top-level JSON elements, * thrown if the JSON string has multiple top-level JSON elements, or if there is trailing data.
* or if there is trailing data.
* *
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}. * <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
* *
* @param reader JSON text * @param reader JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
* @throws JsonParseException if there is an IOException or if the specified * @throws JsonParseException if there is an IOException or if the specified text is not valid
* text is not valid JSON * JSON
* @since 2.8.6 * @since 2.8.6
*/ */
public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException { public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException {
@ -83,16 +83,16 @@ public final class JsonParser {
} }
/** /**
* Returns the next value from the JSON stream as a parse tree. * Returns the next value from the JSON stream as a parse tree. Unlike the other {@code parse}
* Unlike the other {@code parse} methods, no exception is thrown if the JSON data has * methods, no exception is thrown if the JSON data has multiple top-level JSON elements, or if
* multiple top-level JSON elements, or if there is trailing data. * there is trailing data.
* *
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}, * <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode},
* regardless of the strictness setting of the provided reader. The strictness setting * regardless of the strictness setting of the provided reader. The strictness setting of the
* of the reader is restored once this method returns. * reader is restored once this method returns.
* *
* @throws JsonParseException if there is an IOException or if the specified * @throws JsonParseException if there is an IOException or if the specified text is not valid
* text is not valid JSON * JSON
* @since 2.8.6 * @since 2.8.6
*/ */
public static JsonElement parseReader(JsonReader reader) public static JsonElement parseReader(JsonReader reader)
@ -110,21 +110,27 @@ public final class JsonParser {
} }
} }
/** @deprecated Use {@link JsonParser#parseString} */ /**
* @deprecated Use {@link JsonParser#parseString}
*/
@Deprecated @Deprecated
@InlineMe(replacement = "JsonParser.parseString(json)", imports = "com.google.gson.JsonParser") @InlineMe(replacement = "JsonParser.parseString(json)", imports = "com.google.gson.JsonParser")
public JsonElement parse(String json) throws JsonSyntaxException { public JsonElement parse(String json) throws JsonSyntaxException {
return parseString(json); return parseString(json);
} }
/** @deprecated Use {@link JsonParser#parseReader(Reader)} */ /**
* @deprecated Use {@link JsonParser#parseReader(Reader)}
*/
@Deprecated @Deprecated
@InlineMe(replacement = "JsonParser.parseReader(json)", imports = "com.google.gson.JsonParser") @InlineMe(replacement = "JsonParser.parseReader(json)", imports = "com.google.gson.JsonParser")
public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException { public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException {
return parseReader(json); return parseReader(json);
} }
/** @deprecated Use {@link JsonParser#parseReader(JsonReader)} */ /**
* @deprecated Use {@link JsonParser#parseReader(JsonReader)}
*/
@Deprecated @Deprecated
@InlineMe(replacement = "JsonParser.parseReader(json)", imports = "com.google.gson.JsonParser") @InlineMe(replacement = "JsonParser.parseReader(json)", imports = "com.google.gson.JsonParser")
public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException { public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException {

View File

@ -23,9 +23,8 @@ import java.math.BigInteger;
import java.util.Objects; import java.util.Objects;
/** /**
* A class representing a JSON primitive value. A primitive value * A class representing a JSON primitive value. A primitive value is either a String, a Java
* is either a String, a Java primitive, or a Java primitive * primitive, or a Java primitive wrapper type.
* wrapper type.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -97,10 +96,10 @@ public final class JsonPrimitive extends JsonElement {
} }
/** /**
* Convenience method to get this element as a boolean value. * Convenience method to get this element as a boolean value. If this primitive {@linkplain
* If this primitive {@linkplain #isBoolean() is not a boolean}, the string value * #isBoolean() is not a boolean}, the string value is parsed using {@link
* is parsed using {@link Boolean#parseBoolean(String)}. This means {@code "true"} (ignoring * Boolean#parseBoolean(String)}. This means {@code "true"} (ignoring case) is considered {@code
* case) is considered {@code true} and any other value is considered {@code false}. * true} and any other value is considered {@code false}.
*/ */
@Override @Override
public boolean getAsBoolean() { public boolean getAsBoolean() {
@ -121,10 +120,9 @@ public final class JsonPrimitive extends JsonElement {
} }
/** /**
* Convenience method to get this element as a {@link Number}. * Convenience method to get this element as a {@link Number}. If this primitive {@linkplain
* If this primitive {@linkplain #isString() is a string}, a lazily parsed {@code Number} * #isString() is a string}, a lazily parsed {@code Number} is constructed which parses the string
* is constructed which parses the string when any of its methods are called (which can * when any of its methods are called (which can lead to a {@link NumberFormatException}).
* lead to a {@link NumberFormatException}).
* *
* @throws UnsupportedOperationException if this primitive is neither a number nor a string. * @throws UnsupportedOperationException if this primitive is neither a number nor a string.
*/ */
@ -173,7 +171,9 @@ public final class JsonPrimitive extends JsonElement {
*/ */
@Override @Override
public BigDecimal getAsBigDecimal() { public BigDecimal getAsBigDecimal() {
return value instanceof BigDecimal ? (BigDecimal) value : NumberLimits.parseBigDecimal(getAsString()); return value instanceof BigDecimal
? (BigDecimal) value
: NumberLimits.parseBigDecimal(getAsString());
} }
/** /**
@ -232,8 +232,7 @@ public final class JsonPrimitive extends JsonElement {
} }
/** /**
* @throws UnsupportedOperationException if the string value of this * @throws UnsupportedOperationException if the string value of this primitive is empty.
* primitive is empty.
* @deprecated This method is misleading, as it does not get this element as a char but rather as * @deprecated This method is misleading, as it does not get this element as a char but rather as
* a string's first character. * a string's first character.
*/ */
@ -248,9 +247,7 @@ public final class JsonPrimitive extends JsonElement {
} }
} }
/** /** Returns the hash code of this object. */
* Returns the hash code of this object.
*/
@Override @Override
public int hashCode() { public int hashCode() {
if (value == null) { if (value == null) {
@ -269,9 +266,8 @@ public final class JsonPrimitive extends JsonElement {
} }
/** /**
* Returns whether the other object is equal to this. This method only considers * Returns whether the other object is equal to this. This method only considers the other object
* the other object to be equal if it is an instance of {@code JsonPrimitive} and * to be equal if it is an instance of {@code JsonPrimitive} and has an equal value.
* has an equal value.
*/ */
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
@ -281,7 +277,7 @@ public final class JsonPrimitive extends JsonElement {
if (obj == null || getClass() != obj.getClass()) { if (obj == null || getClass() != obj.getClass()) {
return false; return false;
} }
JsonPrimitive other = (JsonPrimitive)obj; JsonPrimitive other = (JsonPrimitive) obj;
if (value == null) { if (value == null) {
return other.value == null; return other.value == null;
} }
@ -301,14 +297,17 @@ public final class JsonPrimitive extends JsonElement {
} }
/** /**
* Returns true if the specified number is an integral type * Returns true if the specified number is an integral type (Long, Integer, Short, Byte,
* (Long, Integer, Short, Byte, BigInteger) * BigInteger)
*/ */
private static boolean isIntegral(JsonPrimitive primitive) { private static boolean isIntegral(JsonPrimitive primitive) {
if (primitive.value instanceof Number) { if (primitive.value instanceof Number) {
Number number = (Number) primitive.value; Number number = (Number) primitive.value;
return number instanceof BigInteger || number instanceof Long || number instanceof Integer return number instanceof BigInteger
|| number instanceof Short || number instanceof Byte; || number instanceof Long
|| number instanceof Integer
|| number instanceof Short
|| number instanceof Byte;
} }
return false; return false;
} }

View File

@ -19,8 +19,8 @@ package com.google.gson;
import java.lang.reflect.Type; import java.lang.reflect.Type;
/** /**
* Context for serialization that is passed to a custom serializer during invocation of its * Context for serialization that is passed to a custom serializer during invocation of its {@link
* {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. * JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -36,10 +36,10 @@ public interface JsonSerializationContext {
public JsonElement serialize(Object src); public JsonElement serialize(Object src);
/** /**
* Invokes default serialization on the specified object passing the specific type information. * Invokes default serialization on the specified object passing the specific type information. It
* It should never be invoked on the element received as a parameter of the * should never be invoked on the element received as a parameter of the {@link
* {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. Doing * JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. Doing so will result
* so will result in an infinite loop since Gson will in-turn call the custom serializer again. * in an infinite loop since Gson will in-turn call the custom serializer again.
* *
* @param src the object that needs to be serialized. * @param src the object that needs to be serialized.
* @param typeOfSrc the actual genericized type of src object. * @param typeOfSrc the actual genericized type of src object.

View File

@ -19,12 +19,12 @@ package com.google.gson;
import java.lang.reflect.Type; import java.lang.reflect.Type;
/** /**
* Interface representing a custom serializer for JSON. You should write a custom serializer, if * Interface representing a custom serializer for JSON. You should write a custom serializer, if you
* you are not happy with the default serialization done by Gson. You will also need to register * are not happy with the default serialization done by Gson. You will also need to register this
* this serializer through {@link com.google.gson.GsonBuilder#registerTypeAdapter(Type, Object)}. * serializer through {@link com.google.gson.GsonBuilder#registerTypeAdapter(Type, Object)}.
* *
* <p>Let us look at example where defining a serializer will be useful. The {@code Id} class * <p>Let us look at example where defining a serializer will be useful. The {@code Id} class
* defined below has two fields: {@code clazz} and {@code value}.</p> * defined below has two fields: {@code clazz} and {@code value}.
* *
* <pre> * <pre>
* public class Id&lt;T&gt; { * public class Id&lt;T&gt; {
@ -42,10 +42,9 @@ import java.lang.reflect.Type;
* } * }
* </pre> * </pre>
* *
* <p>The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be * <p>The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be <code>
* <code>{"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you just want the output to be * {"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you just want the output to be the value
* the value instead, which is {@code 20} in this case. You can achieve that by writing a custom * instead, which is {@code 20} in this case. You can achieve that by writing a custom serializer:
* serializer:</p>
* *
* <pre> * <pre>
* class IdSerializer implements JsonSerializer&lt;Id&gt; { * class IdSerializer implements JsonSerializer&lt;Id&gt; {
@ -55,20 +54,20 @@ import java.lang.reflect.Type;
* } * }
* </pre> * </pre>
* *
* <p>You will also need to register {@code IdSerializer} with Gson as follows:</p> * <p>You will also need to register {@code IdSerializer} with Gson as follows:
*
* <pre> * <pre>
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create(); * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create();
* </pre> * </pre>
* *
* <p>Serializers should be stateless and thread-safe, otherwise the thread-safety * <p>Serializers should be stateless and thread-safe, otherwise the thread-safety guarantees of
* guarantees of {@link Gson} might not apply. * {@link Gson} might not apply.
* *
* <p>New applications should prefer {@link TypeAdapter}, whose streaming API * <p>New applications should prefer {@link TypeAdapter}, whose streaming API is more efficient than
* is more efficient than this interface's tree API. * this interface's tree API.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*
* @param <T> type for which the serializer is being registered. It is possible that a serializer * @param <T> type for which the serializer is being registered. It is possible that a serializer
* may be asked to serialize a specific generic type of the T. * may be asked to serialize a specific generic type of the T.
*/ */
@ -78,11 +77,11 @@ public interface JsonSerializer<T> {
* Gson invokes this call-back method during serialization when it encounters a field of the * Gson invokes this call-back method during serialization when it encounters a field of the
* specified type. * specified type.
* *
* <p>In the implementation of this call-back method, you should consider invoking * <p>In the implementation of this call-back method, you should consider invoking {@link
* {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any * JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
* non-trivial field of the {@code src} object. However, you should never invoke it on the * non-trivial field of the {@code src} object. However, you should never invoke it on the {@code
* {@code src} object itself since that will cause an infinite loop (Gson will call your * src} object itself since that will cause an infinite loop (Gson will call your call-back method
* call-back method again).</p> * again).
* *
* @param src the object that needs to be converted to Json. * @param src the object that needs to be converted to Json.
* @param typeOfSrc the actual type (fully genericized version) of the source object. * @param typeOfSrc the actual type (fully genericized version) of the source object.

View File

@ -27,8 +27,8 @@ import java.util.NoSuchElementException;
/** /**
* A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader * A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader
* asynchronously. The JSON data is parsed in lenient mode, see also * asynchronously. The JSON data is parsed in lenient mode, see also {@link
* {@link JsonReader#setStrictness(Strictness)}. * JsonReader#setStrictness(Strictness)}.
* *
* <p>This class is conditionally thread-safe (see Item 70, Effective Java second edition). To * <p>This class is conditionally thread-safe (see Item 70, Effective Java second edition). To
* properly use this class across multiple threads, you will need to add some external * properly use this class across multiple threads, you will need to add some external
@ -71,8 +71,8 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
} }
/** /**
* Returns the next available {@link JsonElement} on the reader. Throws a * Returns the next available {@link JsonElement} on the reader. Throws a {@link
* {@link NoSuchElementException} if no element is available. * NoSuchElementException} if no element is available.
* *
* @return the next available {@code JsonElement} on the reader. * @return the next available {@code JsonElement} on the reader.
* @throws JsonParseException if the incoming stream is malformed JSON. * @throws JsonParseException if the incoming stream is malformed JSON.
@ -96,6 +96,7 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
/** /**
* Returns true if a {@link JsonElement} is available on the input for consumption * Returns true if a {@link JsonElement} is available on the input for consumption
*
* @return true if a {@link JsonElement} is available on the input, false otherwise * @return true if a {@link JsonElement} is available on the input, false otherwise
* @throws JsonParseException if the incoming stream is malformed JSON. * @throws JsonParseException if the incoming stream is malformed JSON.
* @since 1.4 * @since 1.4
@ -116,6 +117,7 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
/** /**
* This optional {@link Iterator} method is not relevant for stream parsing and hence is not * This optional {@link Iterator} method is not relevant for stream parsing and hence is not
* implemented. * implemented.
*
* @since 1.4 * @since 1.4
*/ */
@Override @Override

View File

@ -16,8 +16,7 @@
package com.google.gson; package com.google.gson;
/** /**
* This exception is raised when Gson attempts to read (or write) a malformed * This exception is raised when Gson attempts to read (or write) a malformed JSON element.
* JSON element.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -35,9 +34,8 @@ public final class JsonSyntaxException extends JsonParseException {
} }
/** /**
* Creates exception with the specified cause. Consider using * Creates exception with the specified cause. Consider using {@link #JsonSyntaxException(String,
* {@link #JsonSyntaxException(String, Throwable)} instead if you can * Throwable)} instead if you can describe what actually happened.
* describe what actually happened.
* *
* @param cause root exception that caused this exception to be thrown. * @param cause root exception that caused this exception to be thrown.
*/ */

View File

@ -20,7 +20,6 @@ package com.google.gson;
* Defines the expected format for a {@code long} or {@code Long} type when it is serialized. * Defines the expected format for a {@code long} or {@code Long} type when it is serialized.
* *
* @since 1.3 * @since 1.3
*
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*/ */
@ -28,13 +27,13 @@ public enum LongSerializationPolicy {
/** /**
* This is the "default" serialization policy that will output a {@code Long} object as a JSON * This is the "default" serialization policy that will output a {@code Long} object as a JSON
* number. For example, assume an object has a long field named "f" then the serialized output * number. For example, assume an object has a long field named "f" then the serialized output
* would be: * would be: {@code {"f":123}}
* {@code {"f":123}}
* *
* <p>A {@code null} value is serialized as {@link JsonNull}. * <p>A {@code null} value is serialized as {@link JsonNull}.
*/ */
DEFAULT() { DEFAULT() {
@Override public JsonElement serialize(Long value) { @Override
public JsonElement serialize(Long value) {
if (value == null) { if (value == null) {
return JsonNull.INSTANCE; return JsonNull.INSTANCE;
} }
@ -44,13 +43,13 @@ public enum LongSerializationPolicy {
/** /**
* Serializes a long value as a quoted string. For example, assume an object has a long field * Serializes a long value as a quoted string. For example, assume an object has a long field
* named "f" then the serialized output would be: * named "f" then the serialized output would be: {@code {"f":"123"}}
* {@code {"f":"123"}}
* *
* <p>A {@code null} value is serialized as {@link JsonNull}. * <p>A {@code null} value is serialized as {@link JsonNull}.
*/ */
STRING() { STRING() {
@Override public JsonElement serialize(Long value) { @Override
public JsonElement serialize(Long value) {
if (value == null) { if (value == null) {
return JsonNull.INSTANCE; return JsonNull.INSTANCE;
} }

View File

@ -20,27 +20,24 @@ import com.google.gson.internal.ReflectionAccessFilterHelper;
import java.lang.reflect.AccessibleObject; import java.lang.reflect.AccessibleObject;
/** /**
* Filter for determining whether reflection based serialization and * Filter for determining whether reflection based serialization and deserialization is allowed for
* deserialization is allowed for a class. * a class.
* *
* <p>A filter can be useful in multiple scenarios, for example when * <p>A filter can be useful in multiple scenarios, for example when upgrading to newer Java
* upgrading to newer Java versions which use the Java Platform Module * versions which use the Java Platform Module System (JPMS). A filter then allows to {@linkplain
* System (JPMS). A filter then allows to {@linkplain FilterResult#BLOCK_INACCESSIBLE * FilterResult#BLOCK_INACCESSIBLE prevent making inaccessible members accessible}, even if the used
* prevent making inaccessible members accessible}, even if the used * Java version might still allow illegal access (but logs a warning), or if {@code java} command
* Java version might still allow illegal access (but logs a warning), * line arguments are used to open the inaccessible packages to other parts of the application. This
* or if {@code java} command line arguments are used to open the inaccessible * interface defines some convenience filters for this task, such as {@link
* packages to other parts of the application. This interface defines some * #BLOCK_INACCESSIBLE_JAVA}.
* convenience filters for this task, such as {@link #BLOCK_INACCESSIBLE_JAVA}.
* *
* <p>A filter can also be useful to prevent mixing model classes of a * <p>A filter can also be useful to prevent mixing model classes of a project with other non-model
* project with other non-model classes; the filter could * classes; the filter could {@linkplain FilterResult#BLOCK_ALL block all reflective access} to
* {@linkplain FilterResult#BLOCK_ALL block all reflective access} to
* non-model classes. * non-model classes.
* *
* <p>A reflection access filter is similar to an {@link ExclusionStrategy} * <p>A reflection access filter is similar to an {@link ExclusionStrategy} with the major
* with the major difference that a filter will cause an exception to be * difference that a filter will cause an exception to be thrown when access is disallowed while an
* thrown when access is disallowed while an exclusion strategy just skips * exclusion strategy just skips fields and classes.
* fields and classes.
* *
* @see GsonBuilder#addReflectionAccessFilter(ReflectionAccessFilter) * @see GsonBuilder#addReflectionAccessFilter(ReflectionAccessFilter)
* @since 2.9.1 * @since 2.9.1
@ -55,71 +52,67 @@ public interface ReflectionAccessFilter {
/** /**
* Reflection access for the class is allowed. * Reflection access for the class is allowed.
* *
* <p>Note that this does not affect the Java access checks in any way, * <p>Note that this does not affect the Java access checks in any way, it only permits Gson to
* it only permits Gson to try using reflection for a class. The Java * try using reflection for a class. The Java runtime might still deny such access.
* runtime might still deny such access.
*/ */
ALLOW, ALLOW,
/** /**
* The filter is indecisive whether reflection access should be allowed. * The filter is indecisive whether reflection access should be allowed. The next registered
* The next registered filter will be consulted to get the result. If * filter will be consulted to get the result. If there is no next filter, this result acts like
* there is no next filter, this result acts like {@link #ALLOW}. * {@link #ALLOW}.
*/ */
INDECISIVE, INDECISIVE,
/** /**
* Blocks reflection access if a member of the class is not accessible * Blocks reflection access if a member of the class is not accessible by default and would have
* by default and would have to be made accessible. This is unaffected * to be made accessible. This is unaffected by any {@code java} command line arguments being
* by any {@code java} command line arguments being used to make packages * used to make packages accessible, or by module declaration directives which <i>open</i> the
* accessible, or by module declaration directives which <i>open</i> the * complete module or certain packages for reflection and will consider such packages
* complete module or certain packages for reflection and will consider * inaccessible.
* such packages inaccessible.
* *
* <p>Note that this <b>only works for Java 9 and higher</b>, for older * <p>Note that this <b>only works for Java 9 and higher</b>, for older Java versions its
* Java versions its functionality will be limited and it might behave like * functionality will be limited and it might behave like {@link #ALLOW}. Access checks are only
* {@link #ALLOW}. Access checks are only performed as defined by the Java * performed as defined by the Java Language Specification (<a
* Language Specification (<a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.6">JLS 11 &sect;6.6</a>), * href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.6">JLS 11
* restrictions imposed by a {@link SecurityManager} are not considered. * &sect;6.6</a>), restrictions imposed by a {@link SecurityManager} are not considered.
* *
* <p>This result type is mainly intended to help enforce the access checks of * <p>This result type is mainly intended to help enforce the access checks of the Java Platform
* the Java Platform Module System. It allows detecting illegal access, even if * Module System. It allows detecting illegal access, even if the used Java version would only
* the used Java version would only log a warning, or is configured to open * log a warning, or is configured to open packages for reflection using command line arguments.
* packages for reflection using command line arguments.
* *
* @see AccessibleObject#canAccess(Object) * @see AccessibleObject#canAccess(Object)
*/ */
BLOCK_INACCESSIBLE, BLOCK_INACCESSIBLE,
/** /**
* Blocks all reflection access for the class. Other means for serializing * Blocks all reflection access for the class. Other means for serializing and deserializing the
* and deserializing the class, such as a {@link TypeAdapter}, have to * class, such as a {@link TypeAdapter}, have to be used.
* be used.
*/ */
BLOCK_ALL BLOCK_ALL
} }
/** /**
* Blocks all reflection access to members of standard Java classes which are * Blocks all reflection access to members of standard Java classes which are not accessible by
* not accessible by default. However, reflection access is still allowed for * default. However, reflection access is still allowed for classes for which all fields are
* classes for which all fields are accessible and which have an accessible * accessible and which have an accessible no-args constructor (or for which an {@link
* no-args constructor (or for which an {@link InstanceCreator} has been registered). * InstanceCreator} has been registered).
* *
* <p>If this filter encounters a class other than a standard Java class it * <p>If this filter encounters a class other than a standard Java class it returns {@link
* returns {@link FilterResult#INDECISIVE}. * FilterResult#INDECISIVE}.
* *
* <p>This filter is mainly intended to help enforcing the access checks of * <p>This filter is mainly intended to help enforcing the access checks of Java Platform Module
* Java Platform Module System. It allows detecting illegal access, even if * System. It allows detecting illegal access, even if the used Java version would only log a
* the used Java version would only log a warning, or is configured to open * warning, or is configured to open packages for reflection. However, this filter <b>only works
* packages for reflection. However, this filter <b>only works for Java 9 and * for Java 9 and higher</b>, when using an older Java version its functionality will be limited.
* higher</b>, when using an older Java version its functionality will be
* limited.
* *
* <p>Note that this filter might not cover all standard Java classes. Currently * <p>Note that this filter might not cover all standard Java classes. Currently only classes in a
* only classes in a {@code java.*} or {@code javax.*} package are considered. The * {@code java.*} or {@code javax.*} package are considered. The set of detected classes might be
* set of detected classes might be expanded in the future without prior notice. * expanded in the future without prior notice.
* *
* @see FilterResult#BLOCK_INACCESSIBLE * @see FilterResult#BLOCK_INACCESSIBLE
*/ */
ReflectionAccessFilter BLOCK_INACCESSIBLE_JAVA = new ReflectionAccessFilter() { ReflectionAccessFilter BLOCK_INACCESSIBLE_JAVA =
@Override public FilterResult check(Class<?> rawClass) { new ReflectionAccessFilter() {
@Override
public FilterResult check(Class<?> rawClass) {
return ReflectionAccessFilterHelper.isJavaType(rawClass) return ReflectionAccessFilterHelper.isJavaType(rawClass)
? FilterResult.BLOCK_INACCESSIBLE ? FilterResult.BLOCK_INACCESSIBLE
: FilterResult.INDECISIVE; : FilterResult.INDECISIVE;
@ -129,22 +122,23 @@ public interface ReflectionAccessFilter {
/** /**
* Blocks all reflection access to members of standard Java classes. * Blocks all reflection access to members of standard Java classes.
* *
* <p>If this filter encounters a class other than a standard Java class it * <p>If this filter encounters a class other than a standard Java class it returns {@link
* returns {@link FilterResult#INDECISIVE}. * FilterResult#INDECISIVE}.
* *
* <p>This filter is mainly intended to prevent depending on implementation * <p>This filter is mainly intended to prevent depending on implementation details of the Java
* details of the Java platform and to help applications prepare for upgrading * platform and to help applications prepare for upgrading to the Java Platform Module System.
* to the Java Platform Module System.
* *
* <p>Note that this filter might not cover all standard Java classes. Currently * <p>Note that this filter might not cover all standard Java classes. Currently only classes in a
* only classes in a {@code java.*} or {@code javax.*} package are considered. The * {@code java.*} or {@code javax.*} package are considered. The set of detected classes might be
* set of detected classes might be expanded in the future without prior notice. * expanded in the future without prior notice.
* *
* @see #BLOCK_INACCESSIBLE_JAVA * @see #BLOCK_INACCESSIBLE_JAVA
* @see FilterResult#BLOCK_ALL * @see FilterResult#BLOCK_ALL
*/ */
ReflectionAccessFilter BLOCK_ALL_JAVA = new ReflectionAccessFilter() { ReflectionAccessFilter BLOCK_ALL_JAVA =
@Override public FilterResult check(Class<?> rawClass) { new ReflectionAccessFilter() {
@Override
public FilterResult check(Class<?> rawClass) {
return ReflectionAccessFilterHelper.isJavaType(rawClass) return ReflectionAccessFilterHelper.isJavaType(rawClass)
? FilterResult.BLOCK_ALL ? FilterResult.BLOCK_ALL
: FilterResult.INDECISIVE; : FilterResult.INDECISIVE;
@ -154,21 +148,23 @@ public interface ReflectionAccessFilter {
/** /**
* Blocks all reflection access to members of standard Android classes. * Blocks all reflection access to members of standard Android classes.
* *
* <p>If this filter encounters a class other than a standard Android class it * <p>If this filter encounters a class other than a standard Android class it returns {@link
* returns {@link FilterResult#INDECISIVE}. * FilterResult#INDECISIVE}.
* *
* <p>This filter is mainly intended to prevent depending on implementation * <p>This filter is mainly intended to prevent depending on implementation details of the Android
* details of the Android platform. * platform.
* *
* <p>Note that this filter might not cover all standard Android classes. Currently * <p>Note that this filter might not cover all standard Android classes. Currently only classes
* only classes in an {@code android.*} or {@code androidx.*} package, and standard * in an {@code android.*} or {@code androidx.*} package, and standard Java classes in a {@code
* Java classes in a {@code java.*} or {@code javax.*} package are considered. The * java.*} or {@code javax.*} package are considered. The set of detected classes might be
* set of detected classes might be expanded in the future without prior notice. * expanded in the future without prior notice.
* *
* @see FilterResult#BLOCK_ALL * @see FilterResult#BLOCK_ALL
*/ */
ReflectionAccessFilter BLOCK_ALL_ANDROID = new ReflectionAccessFilter() { ReflectionAccessFilter BLOCK_ALL_ANDROID =
@Override public FilterResult check(Class<?> rawClass) { new ReflectionAccessFilter() {
@Override
public FilterResult check(Class<?> rawClass) {
return ReflectionAccessFilterHelper.isAndroidType(rawClass) return ReflectionAccessFilterHelper.isAndroidType(rawClass)
? FilterResult.BLOCK_ALL ? FilterResult.BLOCK_ALL
: FilterResult.INDECISIVE; : FilterResult.INDECISIVE;
@ -176,24 +172,26 @@ public interface ReflectionAccessFilter {
}; };
/** /**
* Blocks all reflection access to members of classes belonging to programming * Blocks all reflection access to members of classes belonging to programming language platforms,
* language platforms, such as Java, Android, Kotlin or Scala. * such as Java, Android, Kotlin or Scala.
* *
* <p>If this filter encounters a class other than a standard platform class it * <p>If this filter encounters a class other than a standard platform class it returns {@link
* returns {@link FilterResult#INDECISIVE}. * FilterResult#INDECISIVE}.
* *
* <p>This filter is mainly intended to prevent depending on implementation * <p>This filter is mainly intended to prevent depending on implementation details of the
* details of the platform classes. * platform classes.
* *
* <p>Note that this filter might not cover all platform classes. Currently it * <p>Note that this filter might not cover all platform classes. Currently it combines the
* combines the filters {@link #BLOCK_ALL_JAVA} and {@link #BLOCK_ALL_ANDROID}, * filters {@link #BLOCK_ALL_JAVA} and {@link #BLOCK_ALL_ANDROID}, and checks for other
* and checks for other language-specific platform classes like {@code kotlin.*}. * language-specific platform classes like {@code kotlin.*}. The set of detected classes might be
* The set of detected classes might be expanded in the future without prior notice. * expanded in the future without prior notice.
* *
* @see FilterResult#BLOCK_ALL * @see FilterResult#BLOCK_ALL
*/ */
ReflectionAccessFilter BLOCK_ALL_PLATFORM = new ReflectionAccessFilter() { ReflectionAccessFilter BLOCK_ALL_PLATFORM =
@Override public FilterResult check(Class<?> rawClass) { new ReflectionAccessFilter() {
@Override
public FilterResult check(Class<?> rawClass) {
return ReflectionAccessFilterHelper.isAnyPlatformType(rawClass) return ReflectionAccessFilterHelper.isAnyPlatformType(rawClass)
? FilterResult.BLOCK_ALL ? FilterResult.BLOCK_ALL
: FilterResult.INDECISIVE; : FilterResult.INDECISIVE;
@ -203,10 +201,8 @@ public interface ReflectionAccessFilter {
/** /**
* Checks if reflection access should be allowed for a class. * Checks if reflection access should be allowed for a class.
* *
* @param rawClass * @param rawClass Class to check
* Class to check * @return Result indicating whether reflection access is allowed
* @return
* Result indicating whether reflection access is allowed
*/ */
FilterResult check(Class<?> rawClass); FilterResult check(Class<?> rawClass);
} }

View File

@ -4,14 +4,13 @@ import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
/** /**
* Modes that indicate how strictly a JSON {@linkplain JsonReader reader} or * Modes that indicate how strictly a JSON {@linkplain JsonReader reader} or {@linkplain JsonWriter
* {@linkplain JsonWriter writer} follows the syntax laid out in the * writer} follows the syntax laid out in the <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC
* <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON specification</a>. * 8259 JSON specification</a>.
* *
* <p>You can look at {@link JsonReader#setStrictness(Strictness)} to see how the strictness * <p>You can look at {@link JsonReader#setStrictness(Strictness)} to see how the strictness affects
* affects the {@link JsonReader} and you can look at * the {@link JsonReader} and you can look at {@link JsonWriter#setStrictness(Strictness)} to see
* {@link JsonWriter#setStrictness(Strictness)} to see how the strictness * how the strictness affects the {@link JsonWriter}.
* affects the {@link JsonWriter}.</p>
* *
* @see JsonReader#setStrictness(Strictness) * @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness) * @see JsonWriter#setStrictness(Strictness)

View File

@ -24,9 +24,9 @@ import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
/** /**
* An enumeration that defines two standard number reading strategies and a couple of * An enumeration that defines two standard number reading strategies and a couple of strategies to
* strategies to overcome some historical Gson limitations while deserializing numbers as * overcome some historical Gson limitations while deserializing numbers as {@link Object} and
* {@link Object} and {@link Number}. * {@link Number}.
* *
* @see ToNumberStrategy * @see ToNumberStrategy
* @since 2.8.9 * @since 2.8.9
@ -34,37 +34,39 @@ import java.math.BigDecimal;
public enum ToNumberPolicy implements ToNumberStrategy { public enum ToNumberPolicy implements ToNumberStrategy {
/** /**
* Using this policy will ensure that numbers will be read as {@link Double} values. * Using this policy will ensure that numbers will be read as {@link Double} values. This is the
* This is the default strategy used during deserialization of numbers as {@link Object}. * default strategy used during deserialization of numbers as {@link Object}.
*/ */
DOUBLE { DOUBLE {
@Override public Double readNumber(JsonReader in) throws IOException { @Override
public Double readNumber(JsonReader in) throws IOException {
return in.nextDouble(); return in.nextDouble();
} }
}, },
/** /**
* Using this policy will ensure that numbers will be read as a lazily parsed number backed * Using this policy will ensure that numbers will be read as a lazily parsed number backed by a
* by a string. This is the default strategy used during deserialization of numbers as * string. This is the default strategy used during deserialization of numbers as {@link Number}.
* {@link Number}.
*/ */
LAZILY_PARSED_NUMBER { LAZILY_PARSED_NUMBER {
@Override public Number readNumber(JsonReader in) throws IOException { @Override
public Number readNumber(JsonReader in) throws IOException {
return new LazilyParsedNumber(in.nextString()); return new LazilyParsedNumber(in.nextString());
} }
}, },
/** /**
* Using this policy will ensure that numbers will be read as {@link Long} or {@link Double} * 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 * values depending on how JSON numbers are represented: {@code Long} if the JSON number can be
* be parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a * parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a {@code
* {@code Double} value. If the parsed double-precision number results in a positive or negative * Double} value. If the parsed double-precision number results in a positive or negative infinity
* infinity ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the * ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the {@code
* {@code JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException} * JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException} is
* is thrown. * thrown.
*/ */
LONG_OR_DOUBLE { LONG_OR_DOUBLE {
@Override public Number readNumber(JsonReader in) throws IOException, JsonParseException { @Override
public Number readNumber(JsonReader in) throws IOException, JsonParseException {
String value = in.nextString(); String value = in.nextString();
try { try {
return Long.parseLong(value); return Long.parseLong(value);
@ -72,29 +74,32 @@ public enum ToNumberPolicy implements ToNumberStrategy {
try { try {
Double d = Double.valueOf(value); Double d = Double.valueOf(value);
if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) {
throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath()); throw new MalformedJsonException(
"JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath());
} }
return d; return d;
} catch (NumberFormatException doubleE) { } catch (NumberFormatException doubleE) {
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE); throw new JsonParseException(
"Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE);
} }
} }
} }
}, },
/** /**
* Using this policy will ensure that numbers will be read as numbers of arbitrary length * Using this policy will ensure that numbers will be read as numbers of arbitrary length using
* using {@link BigDecimal}. * {@link BigDecimal}.
*/ */
BIG_DECIMAL { BIG_DECIMAL {
@Override public BigDecimal readNumber(JsonReader in) throws IOException { @Override
public BigDecimal readNumber(JsonReader in) throws IOException {
String value = in.nextString(); String value = in.nextString();
try { try {
return NumberLimits.parseBigDecimal(value); return NumberLimits.parseBigDecimal(value);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), e); throw new JsonParseException(
"Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
} }
} }
} }
} }

View File

@ -20,20 +20,20 @@ import com.google.gson.stream.JsonReader;
import java.io.IOException; import java.io.IOException;
/** /**
* A strategy that is used to control how numbers should be deserialized for {@link Object} and {@link Number} * A strategy that is used to control how numbers should be deserialized for {@link Object} and
* when a concrete type of the deserialized number is unknown in advance. By default, Gson uses the following * {@link Number} when a concrete type of the deserialized number is unknown in advance. By default,
* deserialization strategies: * Gson uses the following deserialization strategies:
* *
* <ul> * <ul>
* <li>{@link Double} values are returned for JSON numbers if the deserialization type is declared as * <li>{@link Double} values are returned for JSON numbers if the deserialization type is declared
* {@code Object}, see {@link ToNumberPolicy#DOUBLE};</li> * as {@code Object}, see {@link ToNumberPolicy#DOUBLE};
* <li>Lazily parsed number values are returned if the deserialization type is declared as {@code Number}, * <li>Lazily parsed number values are returned if the deserialization type is declared as {@code
* see {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.</li> * Number}, see {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.
* </ul> * </ul>
* *
* <p>For historical reasons, Gson does not support deserialization of arbitrary-length numbers for * <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, * {@code Object} and {@code Number} by default, potentially causing precision loss. However, <a
* <a href="https://tools.ietf.org/html/rfc8259#section-6">RFC 8259</a> permits this: * href="https://tools.ietf.org/html/rfc8259#section-6">RFC 8259</a> permits this:
* *
* <pre> * <pre>
* This specification allows implementations to set limits on the range * This specification allows implementations to set limits on the range
@ -50,7 +50,7 @@ import java.io.IOException;
* </pre> * </pre>
* *
* <p>To overcome the precision loss, use for example {@link ToNumberPolicy#LONG_OR_DOUBLE} or * <p>To overcome the precision loss, use for example {@link ToNumberPolicy#LONG_OR_DOUBLE} or
* {@link ToNumberPolicy#BIG_DECIMAL}.</p> * {@link ToNumberPolicy#BIG_DECIMAL}.
* *
* @see ToNumberPolicy * @see ToNumberPolicy
* @see GsonBuilder#setObjectToNumberStrategy(ToNumberStrategy) * @see GsonBuilder#setObjectToNumberStrategy(ToNumberStrategy)
@ -60,8 +60,8 @@ import java.io.IOException;
public interface ToNumberStrategy { public interface ToNumberStrategy {
/** /**
* Reads a number from the given JSON reader. A strategy is supposed to read a single value from the * Reads a number from the given JSON reader. A strategy is supposed to read a single value from
* reader, and the read value is guaranteed never to be {@code null}. * the reader, and the read value is guaranteed never to be {@code null}.
* *
* @param in JSON reader to read a number from * @param in JSON reader to read a number from
* @return number read from the JSON reader. * @return number read from the JSON reader.

View File

@ -31,10 +31,12 @@ import java.io.Writer;
* Converts Java objects to and from JSON. * Converts Java objects to and from JSON.
* *
* <h2>Defining a type's JSON form</h2> * <h2>Defining a type's JSON form</h2>
* By default Gson converts application classes to JSON using its built-in type *
* adapters. If Gson's default JSON conversion isn't appropriate for a type, * By default Gson converts application classes to JSON using its built-in type adapters. If Gson's
* extend this class to customize the conversion. Here's an example of a type * default JSON conversion isn't appropriate for a type, extend this class to customize the
* adapter for an (X,Y) coordinate point: <pre>{@code * conversion. Here's an example of a type adapter for an (X,Y) coordinate point:
*
* <pre>{@code
* public class PointAdapter extends TypeAdapter<Point> { * public class PointAdapter extends TypeAdapter<Point> {
* public Point read(JsonReader reader) throws IOException { * public Point read(JsonReader reader) throws IOException {
* if (reader.peek() == JsonToken.NULL) { * if (reader.peek() == JsonToken.NULL) {
@ -55,36 +57,37 @@ import java.io.Writer;
* String xy = value.getX() + "," + value.getY(); * String xy = value.getX() + "," + value.getY();
* writer.value(xy); * writer.value(xy);
* } * }
* }}</pre> * }
* With this type adapter installed, Gson will convert {@code Points} to JSON as * }</pre>
* strings like {@code "5,8"} rather than objects like {@code {"x":5,"y":8}}. In
* this case the type adapter binds a rich Java class to a compact JSON value.
* *
* <p>The {@link #read(JsonReader) read()} method must read exactly one value * With this type adapter installed, Gson will convert {@code Points} to JSON as strings like {@code
* and {@link #write(JsonWriter,Object) write()} must write exactly one value. * "5,8"} rather than objects like {@code {"x":5,"y":8}}. In this case the type adapter binds a rich
* For primitive types this is means readers should make exactly one call to * Java class to a compact JSON value.
* {@code nextBoolean()}, {@code nextDouble()}, {@code nextInt()}, {@code
* nextLong()}, {@code nextString()} or {@code nextNull()}. Writers should make
* exactly one call to one of <code>value()</code> or <code>nullValue()</code>.
* For arrays, type adapters should start with a call to {@code beginArray()},
* convert all elements, and finish with a call to {@code endArray()}. For
* objects, they should start with {@code beginObject()}, convert the object,
* and finish with {@code endObject()}. Failing to convert a value or converting
* too many values may cause the application to crash.
* *
* <p>Type adapters should be prepared to read null from the stream and write it * <p>The {@link #read(JsonReader) read()} method must read exactly one value and {@link
* to the stream. Alternatively, they should use {@link #nullSafe()} method while * #write(JsonWriter,Object) write()} must write exactly one value. For primitive types this is
* registering the type adapter with Gson. If your {@code Gson} instance * means readers should make exactly one call to {@code nextBoolean()}, {@code nextDouble()}, {@code
* has been configured to {@link GsonBuilder#serializeNulls()}, these nulls will be * nextInt()}, {@code nextLong()}, {@code nextString()} or {@code nextNull()}. Writers should make
* written to the final document. Otherwise the value (and the corresponding name * exactly one call to one of <code>value()</code> or <code>nullValue()</code>. For arrays, type
* when writing to a JSON object) will be omitted automatically. In either case * adapters should start with a call to {@code beginArray()}, convert all elements, and finish with
* your type adapter must handle null. * a call to {@code endArray()}. For objects, they should start with {@code beginObject()}, convert
* the object, and finish with {@code endObject()}. Failing to convert a value or converting too
* many values may cause the application to crash.
* *
* <p>Type adapters should be stateless and thread-safe, otherwise the thread-safety * <p>Type adapters should be prepared to read null from the stream and write it to the stream.
* guarantees of {@link Gson} might not apply. * Alternatively, they should use {@link #nullSafe()} method while registering the type adapter with
* Gson. If your {@code Gson} instance has been configured to {@link GsonBuilder#serializeNulls()},
* these nulls will be written to the final document. Otherwise the value (and the corresponding
* name when writing to a JSON object) will be omitted automatically. In either case your type
* adapter must handle null.
* *
* <p>To use a custom type adapter with Gson, you must <i>register</i> it with a * <p>Type adapters should be stateless and thread-safe, otherwise the thread-safety guarantees of
* {@link GsonBuilder}: <pre>{@code * {@link Gson} might not apply.
*
* <p>To use a custom type adapter with Gson, you must <i>register</i> it with a {@link
* GsonBuilder}:
*
* <pre>{@code
* GsonBuilder builder = new GsonBuilder(); * GsonBuilder builder = new GsonBuilder();
* builder.registerTypeAdapter(Point.class, new PointAdapter()); * builder.registerTypeAdapter(Point.class, new PointAdapter());
* // if PointAdapter didn't check for nulls in its read/write methods, you should instead use * // if PointAdapter didn't check for nulls in its read/write methods, you should instead use
@ -117,12 +120,10 @@ import java.io.Writer;
// //
public abstract class TypeAdapter<T> { public abstract class TypeAdapter<T> {
public TypeAdapter() { public TypeAdapter() {}
}
/** /**
* Writes one JSON value (an array, object, string, number, boolean or null) * Writes one JSON value (an array, object, string, number, boolean or null) for {@code value}.
* for {@code value}.
* *
* @param value the Java object to write. May be null. * @param value the Java object to write. May be null.
*/ */
@ -131,9 +132,9 @@ public abstract class TypeAdapter<T> {
/** /**
* Converts {@code value} to a JSON document and writes it to {@code out}. * Converts {@code value} to a JSON document and writes it to {@code out}.
* *
* <p>A {@link JsonWriter} with default configuration is used for writing the * <p>A {@link JsonWriter} with default configuration is used for writing the JSON data. To
* JSON data. To customize this behavior, create a {@link JsonWriter}, configure * customize this behavior, create a {@link JsonWriter}, configure it and then use {@link
* it and then use {@link #write(JsonWriter, Object)} instead. * #write(JsonWriter, Object)} instead.
* *
* @param value the Java object to convert. May be {@code null}. * @param value the Java object to convert. May be {@code null}.
* @since 2.2 * @since 2.2
@ -144,9 +145,9 @@ public abstract class TypeAdapter<T> {
} }
/** /**
* This wrapper method is used to make a type adapter null tolerant. In general, a * This wrapper method is used to make a type adapter null tolerant. In general, a type adapter is
* type adapter is required to handle nulls in write and read methods. Here is how this * required to handle nulls in write and read methods. Here is how this is typically done:<br>
* is typically done:<br> *
* <pre>{@code * <pre>{@code
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, * Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
* new TypeAdapter<Foo>() { * new TypeAdapter<Foo>() {
@ -166,8 +167,10 @@ public abstract class TypeAdapter<T> {
* } * }
* }).create(); * }).create();
* }</pre> * }</pre>
* You can avoid this boilerplate handling of nulls by wrapping your type adapter with *
* this method. Here is how we will rewrite the above example: * You can avoid this boilerplate handling of nulls by wrapping your type adapter with this
* method. Here is how we will rewrite the above example:
*
* <pre>{@code * <pre>{@code
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, * Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
* new TypeAdapter<Foo>() { * new TypeAdapter<Foo>() {
@ -179,18 +182,22 @@ public abstract class TypeAdapter<T> {
* } * }
* }.nullSafe()).create(); * }.nullSafe()).create();
* }</pre> * }</pre>
*
* Note that we didn't need to check for nulls in our type adapter after we used nullSafe. * Note that we didn't need to check for nulls in our type adapter after we used nullSafe.
*/ */
public final TypeAdapter<T> nullSafe() { public final TypeAdapter<T> nullSafe() {
return new TypeAdapter<T>() { return new TypeAdapter<T>() {
@Override public void write(JsonWriter out, T value) throws IOException { @Override
public void write(JsonWriter out, T value) throws IOException {
if (value == null) { if (value == null) {
out.nullValue(); out.nullValue();
} else { } else {
TypeAdapter.this.write(out, value); TypeAdapter.this.write(out, value);
} }
} }
@Override public T read(JsonReader reader) throws IOException {
@Override
public T read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) { if (reader.peek() == JsonToken.NULL) {
reader.nextNull(); reader.nextNull();
return null; return null;
@ -203,11 +210,12 @@ public abstract class TypeAdapter<T> {
/** /**
* Converts {@code value} to a JSON document. * Converts {@code value} to a JSON document.
* *
* <p>A {@link JsonWriter} with default configuration is used for writing the * <p>A {@link JsonWriter} with default configuration is used for writing the JSON data. To
* JSON data. To customize this behavior, create a {@link JsonWriter}, configure * customize this behavior, create a {@link JsonWriter}, configure it and then use {@link
* it and then use {@link #write(JsonWriter, Object)} instead. * #write(JsonWriter, Object)} instead.
* *
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)} * @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter,
* Object)}
* @param value the Java object to convert. May be {@code null}. * @param value the Java object to convert. May be {@code null}.
* @since 2.2 * @since 2.2
*/ */
@ -226,7 +234,8 @@ public abstract class TypeAdapter<T> {
* *
* @param value the Java object to convert. May be {@code null}. * @param value the Java object to convert. May be {@code null}.
* @return the converted JSON tree. May be {@link JsonNull}. * @return the converted JSON tree. May be {@link JsonNull}.
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)} * @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter,
* Object)}
* @since 2.2 * @since 2.2
*/ */
public final JsonElement toJsonTree(T value) { public final JsonElement toJsonTree(T value) {
@ -240,8 +249,8 @@ public abstract class TypeAdapter<T> {
} }
/** /**
* Reads one JSON value (an array, object, string, number, boolean or null) * Reads one JSON value (an array, object, string, number, boolean or null) and converts it to a
* and converts it to a Java object. Returns the converted object. * Java object. Returns the converted object.
* *
* @return the converted Java object. May be {@code null}. * @return the converted Java object. May be {@code null}.
*/ */
@ -250,13 +259,13 @@ public abstract class TypeAdapter<T> {
/** /**
* Converts the JSON document in {@code in} to a Java object. * Converts the JSON document in {@code in} to a Java object.
* *
* <p>A {@link JsonReader} with default configuration (that is with * <p>A {@link JsonReader} with default configuration (that is with {@link
* {@link Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data. * Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data. To customize this
* To customize this behavior, create a {@link JsonReader}, configure it and then * behavior, create a {@link JsonReader}, configure it and then use {@link #read(JsonReader)}
* use {@link #read(JsonReader)} instead. * instead.
* *
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements, * <p>No exception is thrown if the JSON data has multiple top-level JSON elements, or if there is
* or if there is trailing data. * trailing data.
* *
* @return the converted Java object. May be {@code null}. * @return the converted Java object. May be {@code null}.
* @since 2.2 * @since 2.2
@ -269,13 +278,13 @@ public abstract class TypeAdapter<T> {
/** /**
* Converts the JSON document in {@code json} to a Java object. * Converts the JSON document in {@code json} to a Java object.
* *
* <p>A {@link JsonReader} with default configuration (that is with * <p>A {@link JsonReader} with default configuration (that is with {@link
* {@link Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data. * Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data. To customize this
* To customize this behavior, create a {@link JsonReader}, configure it and then * behavior, create a {@link JsonReader}, configure it and then use {@link #read(JsonReader)}
* use {@link #read(JsonReader)} instead. * instead.
* *
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements, * <p>No exception is thrown if the JSON data has multiple top-level JSON elements, or if there is
* or if there is trailing data. * trailing data.
* *
* @return the converted Java object. May be {@code null}. * @return the converted Java object. May be {@code null}.
* @since 2.2 * @since 2.2

View File

@ -19,16 +19,18 @@ package com.google.gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
/** /**
* Creates type adapters for set of related types. Type adapter factories are * Creates type adapters for set of related types. Type adapter factories are most useful when
* most useful when several types share similar structure in their JSON form. * several types share similar structure in their JSON form.
* *
* <h2>Examples</h2> * <h2>Examples</h2>
* <h3>Example: Converting enums to lowercase</h3>
* In this example, we implement a factory that creates type adapters for all
* enums. The type adapters will write enums in lowercase, despite the fact
* that they're defined in {@code CONSTANT_CASE} in the corresponding Java
* model: <pre> {@code
* *
* <h3>Example: Converting enums to lowercase</h3>
*
* In this example, we implement a factory that creates type adapters for all enums. The type
* adapters will write enums in lowercase, despite the fact that they're defined in {@code
* CONSTANT_CASE} in the corresponding Java model:
*
* <pre>{@code
* public class LowercaseEnumTypeAdapterFactory implements TypeAdapterFactory { * public class LowercaseEnumTypeAdapterFactory implements TypeAdapterFactory {
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { * public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
* Class<T> rawType = (Class<T>) type.getRawType(); * Class<T> rawType = (Class<T>) type.getRawType();
@ -67,42 +69,40 @@ import com.google.gson.reflect.TypeToken;
* } * }
* }</pre> * }</pre>
* *
* <p>Type adapter factories select which types they provide type adapters * <p>Type adapter factories select which types they provide type adapters for. If a factory cannot
* for. If a factory cannot support a given type, it must return null when * support a given type, it must return null when that type is passed to {@link #create}. Factories
* that type is passed to {@link #create}. Factories should expect {@code * should expect {@code create()} to be called on them for many types and should return null for
* create()} to be called on them for many types and should return null for * most of those types. In the above example the factory returns null for calls to {@code create()}
* most of those types. In the above example the factory returns null for * where {@code type} is not an enum.
* calls to {@code create()} where {@code type} is not an enum.
* *
* <p>A factory is typically called once per type, but the returned type * <p>A factory is typically called once per type, but the returned type adapter may be used many
* adapter may be used many times. It is most efficient to do expensive work * times. It is most efficient to do expensive work like reflection in {@code create()} so that the
* like reflection in {@code create()} so that the type adapter's {@code * type adapter's {@code read()} and {@code write()} methods can be very fast. In this example the
* read()} and {@code write()} methods can be very fast. In this example the
* mapping from lowercase name to enum value is computed eagerly. * mapping from lowercase name to enum value is computed eagerly.
* *
* <p>As with type adapters, factories must be <i>registered</i> with a {@link * <p>As with type adapters, factories must be <i>registered</i> with a {@link
* com.google.gson.GsonBuilder} for them to take effect: <pre> {@code * com.google.gson.GsonBuilder} for them to take effect:
* *
* <pre>{@code
* GsonBuilder builder = new GsonBuilder(); * GsonBuilder builder = new GsonBuilder();
* builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory()); * builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
* ... * ...
* Gson gson = builder.create(); * Gson gson = builder.create();
* }</pre> * }</pre>
* If multiple factories support the same type, the factory registered earlier *
* takes precedence. * If multiple factories support the same type, the factory registered earlier takes precedence.
* *
* <h3>Example: Composing other type adapters</h3> * <h3>Example: Composing other type adapters</h3>
* In this example we implement a factory for Guava's {@code Multiset}
* collection type. The factory can be used to create type adapters for
* multisets of any element type: the type adapter for {@code
* Multiset<String>} is different from the type adapter for {@code
* Multiset<URL>}.
* *
* <p>The type adapter <i>delegates</i> to another type adapter for the * In this example we implement a factory for Guava's {@code Multiset} collection type. The factory
* multiset elements. It figures out the element type by reflecting on the * can be used to create type adapters for multisets of any element type: the type adapter for
* multiset's type token. A {@code Gson} is passed in to {@code create} for * {@code Multiset<String>} is different from the type adapter for {@code Multiset<URL>}.
* just this purpose: <pre> {@code
* *
* <p>The type adapter <i>delegates</i> to another type adapter for the multiset elements. It
* figures out the element type by reflecting on the multiset's type token. A {@code Gson} is passed
* in to {@code create} for just this purpose:
*
* <pre>{@code
* public class MultisetTypeAdapterFactory implements TypeAdapterFactory { * public class MultisetTypeAdapterFactory implements TypeAdapterFactory {
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { * public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
* Type type = typeToken.getType(); * Type type = typeToken.getType();
@ -153,19 +153,18 @@ import com.google.gson.reflect.TypeToken;
* } * }
* } * }
* }</pre> * }</pre>
* Delegating from one type adapter to another is extremely powerful; it's *
* the foundation of how Gson converts Java objects and collections. Whenever * Delegating from one type adapter to another is extremely powerful; it's the foundation of how
* possible your factory should retrieve its delegate type adapter in the * Gson converts Java objects and collections. Whenever possible your factory should retrieve its
* {@code create()} method; this ensures potentially-expensive type adapter * delegate type adapter in the {@code create()} method; this ensures potentially-expensive type
* creation happens only once. * adapter creation happens only once.
* *
* @since 2.1 * @since 2.1
*/ */
public interface TypeAdapterFactory { public interface TypeAdapterFactory {
/** /**
* Returns a type adapter for {@code type}, or null if this factory doesn't * Returns a type adapter for {@code type}, or null if this factory doesn't support {@code type}.
* support {@code type}.
*/ */
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type); <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
} }

View File

@ -23,15 +23,15 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* An annotation that indicates this member should be exposed for JSON * An annotation that indicates this member should be exposed for JSON serialization or
* serialization or deserialization. * deserialization.
* *
* <p>This annotation has no effect unless you build {@link com.google.gson.Gson} * <p>This annotation has no effect unless you build {@link com.google.gson.Gson} with a {@link
* with a {@link com.google.gson.GsonBuilder} and invoke * com.google.gson.GsonBuilder} and invoke {@link
* {@link com.google.gson.GsonBuilder#excludeFieldsWithoutExposeAnnotation()} * com.google.gson.GsonBuilder#excludeFieldsWithoutExposeAnnotation()} method.
* method.</p>
* *
* <p>Here is an example of how this annotation is meant to be used: * <p>Here is an example of how this annotation is meant to be used:
*
* <pre> * <pre>
* public class User { * public class User {
* &#64;Expose private String firstName; * &#64;Expose private String firstName;
@ -40,20 +40,21 @@ import java.lang.annotation.Target;
* private String password; * private String password;
* } * }
* </pre> * </pre>
* If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
* methods will use the {@code password} field along-with {@code firstName}, {@code lastName},
* and {@code emailAddress} for serialization and deserialization. However, if you created Gson
* with {@code Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()}
* then the {@code toJson()} and {@code fromJson()} methods of Gson will exclude the
* {@code password} field. This is because the {@code password} field is not marked with the
* {@code @Expose} annotation. Gson will also exclude {@code lastName} and {@code emailAddress}
* from serialization since {@code serialize} is set to {@code false}. Similarly, Gson will
* exclude {@code emailAddress} from deserialization since {@code deserialize} is set to false.
* *
* <p>Note that another way to achieve the same effect would have been to just mark the * If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()} methods
* {@code password} field as {@code transient}, and Gson would have excluded it even with default * will use the {@code password} field along-with {@code firstName}, {@code lastName}, and {@code
* settings. The {@code @Expose} annotation is useful in a style of programming where you want to * emailAddress} for serialization and deserialization. However, if you created Gson with {@code
* explicitly specify all fields that should get considered for serialization or deserialization. * Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()} then the {@code
* toJson()} and {@code fromJson()} methods of Gson will exclude the {@code password} field. This is
* because the {@code password} field is not marked with the {@code @Expose} annotation. Gson will
* also exclude {@code lastName} and {@code emailAddress} from serialization since {@code serialize}
* is set to {@code false}. Similarly, Gson will exclude {@code emailAddress} from deserialization
* since {@code deserialize} is set to false.
*
* <p>Note that another way to achieve the same effect would have been to just mark the {@code
* password} field as {@code transient}, and Gson would have excluded it even with default settings.
* The {@code @Expose} annotation is useful in a style of programming where you want to explicitly
* specify all fields that should get considered for serialization or deserialization.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -67,14 +68,16 @@ public @interface Expose {
* If {@code true}, the field marked with this annotation is written out in the JSON while * If {@code true}, the field marked with this annotation is written out in the JSON while
* serializing. If {@code false}, the field marked with this annotation is skipped from the * serializing. If {@code false}, the field marked with this annotation is skipped from the
* serialized output. Defaults to {@code true}. * serialized output. Defaults to {@code true}.
*
* @since 1.4 * @since 1.4
*/ */
public boolean serialize() default true; public boolean serialize() default true;
/** /**
* If {@code true}, the field marked with this annotation is deserialized from the JSON. * If {@code true}, the field marked with this annotation is deserialized from the JSON. If {@code
* If {@code false}, the field marked with this annotation is skipped during deserialization. * false}, the field marked with this annotation is skipped during deserialization. Defaults to
* Defaults to {@code true}. * {@code true}.
*
* @since 1.4 * @since 1.4
*/ */
public boolean deserialize() default true; public boolean deserialize() default true;

View File

@ -29,10 +29,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* An annotation that indicates the Gson {@link TypeAdapter} to use with a class * An annotation that indicates the Gson {@link TypeAdapter} to use with a class or field.
* or field. *
* <p>Here is an example of how this annotation is used:
* *
* <p>Here is an example of how this annotation is used:</p>
* <pre> * <pre>
* &#64;JsonAdapter(UserJsonAdapter.class) * &#64;JsonAdapter(UserJsonAdapter.class)
* public class User { * public class User {
@ -68,6 +68,7 @@ import java.lang.annotation.Target;
* annotation, it will automatically be invoked to serialize/deserialize {@code User} instances. * annotation, it will automatically be invoked to serialize/deserialize {@code User} instances.
* *
* <p>Here is an example of how to apply this annotation to a field. * <p>Here is an example of how to apply this annotation to a field.
*
* <pre> * <pre>
* private static final class Gadget { * private static final class Gadget {
* &#64;JsonAdapter(UserJsonAdapter.class) * &#64;JsonAdapter(UserJsonAdapter.class)
@ -79,39 +80,34 @@ import java.lang.annotation.Target;
* } * }
* </pre> * </pre>
* *
* It's possible to specify different type adapters on a field, that * It's possible to specify different type adapters on a field, that field's type, and in the {@link
* field's type, and in the {@link GsonBuilder}. Field annotations * GsonBuilder}. Field annotations take precedence over {@code GsonBuilder}-registered type
* take precedence over {@code GsonBuilder}-registered type
* adapters, which in turn take precedence over annotated types. * adapters, which in turn take precedence over annotated types.
* *
* <p>The class referenced by this annotation must be either a {@link * <p>The class referenced by this annotation must be either a {@link TypeAdapter} or a {@link
* TypeAdapter} or a {@link TypeAdapterFactory}, or must implement one * TypeAdapterFactory}, or must implement one or both of {@link JsonDeserializer} or {@link
* or both of {@link JsonDeserializer} or {@link JsonSerializer}. * JsonSerializer}. Using {@link TypeAdapterFactory} makes it possible to delegate to the enclosing
* Using {@link TypeAdapterFactory} makes it possible to delegate * {@link Gson} instance. By default the specified adapter will not be called for {@code null}
* to the enclosing {@link Gson} instance. By default the specified * values; set {@link #nullSafe()} to {@code false} to let the adapter handle {@code null} values
* adapter will not be called for {@code null} values; set {@link #nullSafe()} * itself.
* to {@code false} to let the adapter handle {@code null} values itself. *
* <p>The type adapter is created in the same way Gson creates instances of custom classes during
* deserialization, that means:
* *
* <p>The type adapter is created in the same way Gson creates instances of
* custom classes during deserialization, that means:
* <ol> * <ol>
* <li>If a custom {@link InstanceCreator} has been registered for the * <li>If a custom {@link InstanceCreator} has been registered for the adapter class, it will be
* adapter class, it will be used to create the instance * used to create the instance
* <li>Otherwise, if the adapter class has a no-args constructor * <li>Otherwise, if the adapter class has a no-args constructor (regardless of which visibility),
* (regardless of which visibility), it will be invoked to create * it will be invoked to create the instance
* the instance * <li>Otherwise, JDK {@code Unsafe} will be used to create the instance; see {@link
* <li>Otherwise, JDK {@code Unsafe} will be used to create the instance; * GsonBuilder#disableJdkUnsafe()} for the unexpected side-effects this might have
* see {@link GsonBuilder#disableJdkUnsafe()} for the unexpected
* side-effects this might have
* </ol> * </ol>
* *
* <p>{@code Gson} instances might cache the adapter they create for * <p>{@code Gson} instances might cache the adapter they create for a {@code @JsonAdapter}
* a {@code @JsonAdapter} annotation. It is not guaranteed that a new * annotation. It is not guaranteed that a new adapter is created every time the annotated class or
* adapter is created every time the annotated class or field is serialized * field is serialized or deserialized.
* or deserialized.
* *
* @since 2.3 * @since 2.3
*
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
* @author Jesse Wilson * @author Jesse Wilson
@ -121,16 +117,19 @@ import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.FIELD}) @Target({ElementType.TYPE, ElementType.FIELD})
public @interface JsonAdapter { public @interface JsonAdapter {
/** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link JsonDeserializer} or {@link JsonSerializer}. */ /**
* Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link
* JsonDeserializer} or {@link JsonSerializer}.
*/
Class<?> value(); Class<?> value();
/** /**
* Whether the adapter referenced by {@link #value()} should be made {@linkplain TypeAdapter#nullSafe() null-safe}. * Whether the adapter referenced by {@link #value()} should be made {@linkplain
* TypeAdapter#nullSafe() null-safe}.
* *
* <p>If {@code true} (the default), it will be made null-safe and Gson will handle {@code null} Java objects * <p>If {@code true} (the default), it will be made null-safe and Gson will handle {@code null}
* on serialization and JSON {@code null} on deserialization without calling the adapter. If {@code false}, * Java objects on serialization and JSON {@code null} on deserialization without calling the
* the adapter will have to handle the {@code null} values. * adapter. If {@code false}, the adapter will have to handle the {@code null} values.
*/ */
boolean nullSafe() default true; boolean nullSafe() default true;
} }

View File

@ -23,16 +23,17 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* An annotation that indicates this member should be serialized to JSON with * An annotation that indicates this member should be serialized to JSON with the provided name
* the provided name value as its field name. * value as its field name.
* *
* <p>This annotation will override any {@link com.google.gson.FieldNamingPolicy}, including * <p>This annotation will override any {@link com.google.gson.FieldNamingPolicy}, including the
* the default field naming policy, that may have been set on the {@link com.google.gson.Gson} * default field naming policy, that may have been set on the {@link com.google.gson.Gson} instance.
* instance. A different naming policy can set using the {@code GsonBuilder} class. See * A different naming policy can set using the {@code GsonBuilder} class. See {@link
* {@link com.google.gson.GsonBuilder#setFieldNamingPolicy(com.google.gson.FieldNamingPolicy)} * com.google.gson.GsonBuilder#setFieldNamingPolicy(com.google.gson.FieldNamingPolicy)} for more
* for more information.</p> * information.
*
* <p>Here is an example of how this annotation is meant to be used:
* *
* <p>Here is an example of how this annotation is meant to be used:</p>
* <pre> * <pre>
* public class MyClass { * public class MyClass {
* &#64;SerializedName("name") String a; * &#64;SerializedName("name") String a;
@ -47,8 +48,9 @@ import java.lang.annotation.Target;
* } * }
* </pre> * </pre>
* *
* <p>The following shows the output that is generated when serializing an instance of the * <p>The following shows the output that is generated when serializing an instance of the above
* above example class:</p> * example class:
*
* <pre> * <pre>
* MyClass target = new MyClass("v1", "v2", "v3"); * MyClass target = new MyClass("v1", "v2", "v3");
* Gson gson = new Gson(); * Gson gson = new Gson();
@ -59,9 +61,10 @@ import java.lang.annotation.Target;
* {"name":"v1","name1":"v2","c":"v3"} * {"name":"v1","name1":"v2","c":"v3"}
* </pre> * </pre>
* *
* <p>NOTE: The value you specify in this annotation must be a valid JSON field name.</p> * <p>NOTE: The value you specify in this annotation must be a valid JSON field name. While
* While deserializing, all values specified in the annotation will be deserialized into the field. * deserializing, all values specified in the annotation will be deserialized into the field. For
* For example: * example:
*
* <pre> * <pre>
* MyClass target = gson.fromJson("{'name1':'v1'}", MyClass.class); * MyClass target = gson.fromJson("{'name1':'v1'}", MyClass.class);
* assertEquals("v1", target.b); * assertEquals("v1", target.b);
@ -70,10 +73,10 @@ import java.lang.annotation.Target;
* target = gson.fromJson("{'name3':'v3'}", MyClass.class); * target = gson.fromJson("{'name3':'v3'}", MyClass.class);
* assertEquals("v3", target.b); * assertEquals("v3", target.b);
* </pre> * </pre>
*
* Note that MyClass.b is now deserialized from either name1, name2 or name3. * Note that MyClass.b is now deserialized from either name1, name2 or name3.
* *
* @see com.google.gson.FieldNamingPolicy * @see com.google.gson.FieldNamingPolicy
*
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*/ */
@ -88,6 +91,7 @@ public @interface SerializedName {
* @return the desired name of the field when it is serialized or deserialized * @return the desired name of the field when it is serialized or deserialized
*/ */
String value(); String value();
/** /**
* The alternative names of the field when it is deserialized * The alternative names of the field when it is deserialized
* *

View File

@ -24,14 +24,14 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* An annotation that indicates the version number since a member or a type has been present. * An annotation that indicates the version number since a member or a type has been present. This
* This annotation is useful to manage versioning of your JSON classes for a web-service. * annotation is useful to manage versioning of your JSON classes for a web-service.
* *
* <p> * <p>This annotation has no effect unless you build {@link com.google.gson.Gson} with a {@code
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a * GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
* {@code GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method. *
* <p>Here is an example of how this annotation is meant to be used:
* *
* <p>Here is an example of how this annotation is meant to be used:</p>
* <pre> * <pre>
* public class User { * public class User {
* private String firstName; * private String firstName;
@ -44,9 +44,9 @@ import java.lang.annotation.Target;
* *
* <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()} * <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
* methods will use all the fields for serialization and deserialization. However, if you created * methods will use all the fields for serialization and deserialization. However, if you created
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.0).create()} then the * Gson with {@code Gson gson = new GsonBuilder().setVersion(1.0).create()} then the {@code
* {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code address} field * toJson()} and {@code fromJson()} methods of Gson will exclude the {@code address} field since
* since it's version number is set to {@code 1.1}.</p> * it's version number is set to {@code 1.1}.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -58,8 +58,8 @@ import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.TYPE}) @Target({ElementType.FIELD, ElementType.TYPE})
public @interface Since { public @interface Since {
/** /**
* The value indicating a version number since this member or type has been present. * The value indicating a version number since this member or type has been present. The number is
* The number is inclusive; annotated elements will be included if {@code gsonVersion >= value}. * inclusive; annotated elements will be included if {@code gsonVersion >= value}.
*/ */
double value(); double value();
} }

View File

@ -25,15 +25,15 @@ import java.lang.annotation.Target;
/** /**
* An annotation that indicates the version number until a member or a type should be present. * An annotation that indicates the version number until a member or a type should be present.
* Basically, if Gson is created with a version number that is equal to or exceeds the value * Basically, if Gson is created with a version number that is equal to or exceeds the value stored
* stored in the {@code Until} annotation then the field will be ignored from the JSON output. * in the {@code Until} annotation then the field will be ignored from the JSON output. This
* This annotation is useful to manage versioning of your JSON classes for a web-service. * annotation is useful to manage versioning of your JSON classes for a web-service.
* *
* <p> * <p>This annotation has no effect unless you build {@link com.google.gson.Gson} with a {@code
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a * GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
* {@code GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method. *
* <p>Here is an example of how this annotation is meant to be used:
* *
* <p>Here is an example of how this annotation is meant to be used:</p>
* <pre> * <pre>
* public class User { * public class User {
* private String firstName; * private String firstName;
@ -45,11 +45,11 @@ import java.lang.annotation.Target;
* *
* <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()} * <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
* methods will use all the fields for serialization and deserialization. However, if you created * methods will use all the fields for serialization and deserialization. However, if you created
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the * Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the {@code
* {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress} * toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress} and {@code
* and {@code password} fields from the example above, because the version number passed to the * password} fields from the example above, because the version number passed to the GsonBuilder,
* GsonBuilder, {@code 1.2}, exceeds the version number set on the {@code Until} annotation, * {@code 1.2}, exceeds the version number set on the {@code Until} annotation, {@code 1.1}, for
* {@code 1.1}, for those fields. * those fields.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -63,8 +63,8 @@ import java.lang.annotation.Target;
public @interface Until { public @interface Until {
/** /**
* The value indicating a version number until this member or type should be included. * The value indicating a version number until this member or type should be included. The number
* The number is exclusive; annotated elements will be included if {@code gsonVersion < value}. * is exclusive; annotated elements will be included if {@code gsonVersion < value}.
*/ */
double value(); double value();
} }

View File

@ -37,8 +37,8 @@ public final class $Gson$Preconditions {
} }
/** /**
* @deprecated * @deprecated This is an internal Gson method. Use {@link Objects#requireNonNull(Object)}
* This is an internal Gson method. Use {@link Objects#requireNonNull(Object)} instead. * instead.
*/ */
// Only deprecated for now because external projects might be using this by accident // Only deprecated for now because external projects might be using this by accident
@Deprecated @Deprecated

View File

@ -1,19 +1,16 @@
/** /**
* Copyright (C) 2008 Google Inc. * Copyright (C) 2008 Google Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* you may not use this file except in compliance with the License. * except in compliance with the License. You may obtain a copy of the License at
* You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * <p>http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * <p>Unless required by applicable law or agreed to in writing, software distributed under the
* distributed under the License is distributed on an "AS IS" BASIS, * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * express or implied. See the License for the specific language governing permissions and
* See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.gson.internal; package com.google.gson.internal;
import static com.google.gson.internal.$Gson$Preconditions.checkArgument; import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
@ -50,8 +47,8 @@ public final class $Gson$Types {
} }
/** /**
* Returns a new parameterized type, applying {@code typeArguments} to * Returns a new parameterized type, applying {@code typeArguments} to {@code rawType} and
* {@code rawType} and enclosed by {@code ownerType}. * enclosed by {@code ownerType}.
* *
* @return a {@link java.io.Serializable serializable} parameterized type. * @return a {@link java.io.Serializable serializable} parameterized type.
*/ */
@ -61,8 +58,7 @@ public final class $Gson$Types {
} }
/** /**
* Returns an array type whose elements are all instances of * Returns an array type whose elements are all instances of {@code componentType}.
* {@code componentType}.
* *
* @return a {@link java.io.Serializable serializable} generic array type. * @return a {@link java.io.Serializable serializable} generic array type.
*/ */
@ -71,40 +67,38 @@ public final class $Gson$Types {
} }
/** /**
* Returns a type that represents an unknown type that extends {@code bound}. * Returns a type that represents an unknown type that extends {@code bound}. For example, if
* For example, if {@code bound} is {@code CharSequence.class}, this returns * {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If
* {@code ? extends CharSequence}. If {@code bound} is {@code Object.class}, * {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code ?
* this returns {@code ?}, which is shorthand for {@code ? extends Object}. * extends Object}.
*/ */
public static WildcardType subtypeOf(Type bound) { public static WildcardType subtypeOf(Type bound) {
Type[] upperBounds; Type[] upperBounds;
if (bound instanceof WildcardType) { if (bound instanceof WildcardType) {
upperBounds = ((WildcardType) bound).getUpperBounds(); upperBounds = ((WildcardType) bound).getUpperBounds();
} else { } else {
upperBounds = new Type[] { bound }; upperBounds = new Type[] {bound};
} }
return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY); return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY);
} }
/** /**
* Returns a type that represents an unknown supertype of {@code bound}. For * Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code
* example, if {@code bound} is {@code String.class}, this returns {@code ? * bound} is {@code String.class}, this returns {@code ? super String}.
* super String}.
*/ */
public static WildcardType supertypeOf(Type bound) { public static WildcardType supertypeOf(Type bound) {
Type[] lowerBounds; Type[] lowerBounds;
if (bound instanceof WildcardType) { if (bound instanceof WildcardType) {
lowerBounds = ((WildcardType) bound).getLowerBounds(); lowerBounds = ((WildcardType) bound).getLowerBounds();
} else { } else {
lowerBounds = new Type[] { bound }; lowerBounds = new Type[] {bound};
} }
return new WildcardTypeImpl(new Type[] { Object.class }, lowerBounds); return new WildcardTypeImpl(new Type[] {Object.class}, lowerBounds);
} }
/** /**
* Returns a type that is functionally equal but not necessarily equal * Returns a type that is functionally equal but not necessarily equal according to {@link
* according to {@link Object#equals(Object) Object.equals()}. The returned * Object#equals(Object) Object.equals()}. The returned type is {@link java.io.Serializable}.
* type is {@link java.io.Serializable}.
*/ */
public static Type canonicalize(Type type) { public static Type canonicalize(Type type) {
if (type instanceof Class) { if (type instanceof Class) {
@ -113,8 +107,8 @@ public final class $Gson$Types {
} else if (type instanceof ParameterizedType) { } else if (type instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) type; ParameterizedType p = (ParameterizedType) type;
return new ParameterizedTypeImpl(p.getOwnerType(), return new ParameterizedTypeImpl(
p.getRawType(), p.getActualTypeArguments()); p.getOwnerType(), p.getRawType(), p.getActualTypeArguments());
} else if (type instanceof GenericArrayType) { } else if (type instanceof GenericArrayType) {
GenericArrayType g = (GenericArrayType) type; GenericArrayType g = (GenericArrayType) type;
@ -145,7 +139,7 @@ public final class $Gson$Types {
return (Class<?>) rawType; return (Class<?>) rawType;
} else if (type instanceof GenericArrayType) { } else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType)type).getGenericComponentType(); Type componentType = ((GenericArrayType) type).getGenericComponentType();
return Array.newInstance(getRawType(componentType), 0).getClass(); return Array.newInstance(getRawType(componentType), 0).getClass();
} else if (type instanceof TypeVariable) { } else if (type instanceof TypeVariable) {
@ -161,8 +155,12 @@ public final class $Gson$Types {
} else { } else {
String className = type == null ? "null" : type.getClass().getName(); String className = type == null ? "null" : type.getClass().getName();
throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " throw new IllegalArgumentException(
+ "GenericArrayType, but <" + type + "> is of type " + className); "Expected a Class, ParameterizedType, or "
+ "GenericArrayType, but <"
+ type
+ "> is of type "
+ className);
} }
} }
@ -170,9 +168,7 @@ public final class $Gson$Types {
return Objects.equals(a, b); return Objects.equals(a, b);
} }
/** /** Returns true if {@code a} and {@code b} are equal. */
* Returns true if {@code a} and {@code b} are equal.
*/
public static boolean equals(Type a, Type b) { public static boolean equals(Type a, Type b) {
if (a == b) { if (a == b) {
// also handles (a == null && b == null) // also handles (a == null && b == null)
@ -280,19 +276,23 @@ public final class $Gson$Types {
*/ */
private static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) { private static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
if (context instanceof WildcardType) { if (context instanceof WildcardType) {
// wildcards are useless for resolving supertypes. As the upper bound has the same raw type, use it instead // Wildcards are useless for resolving supertypes. As the upper bound has the same raw type,
Type[] bounds = ((WildcardType)context).getUpperBounds(); // use it instead
Type[] bounds = ((WildcardType) context).getUpperBounds();
// Currently the JLS only permits one bound for wildcards so using first bound is safe // Currently the JLS only permits one bound for wildcards so using first bound is safe
assert bounds.length == 1; assert bounds.length == 1;
context = bounds[0]; context = bounds[0];
} }
checkArgument(supertype.isAssignableFrom(contextRawType)); checkArgument(supertype.isAssignableFrom(contextRawType));
return resolve(context, contextRawType, return resolve(
context,
contextRawType,
$Gson$Types.getGenericSupertype(context, contextRawType, supertype)); $Gson$Types.getGenericSupertype(context, contextRawType, supertype));
} }
/** /**
* Returns the component type of this array type. * Returns the component type of this array type.
*
* @throws ClassCastException if this type is not an array. * @throws ClassCastException if this type is not an array.
*/ */
public static Type getArrayComponentType(Type array) { public static Type getArrayComponentType(Type array) {
@ -303,6 +303,7 @@ public final class $Gson$Types {
/** /**
* Returns the element type of this collection type. * Returns the element type of this collection type.
*
* @throws IllegalArgumentException if this type is not a collection. * @throws IllegalArgumentException if this type is not a collection.
*/ */
public static Type getCollectionElementType(Type context, Class<?> contextRawType) { public static Type getCollectionElementType(Type context, Class<?> contextRawType) {
@ -315,8 +316,8 @@ public final class $Gson$Types {
} }
/** /**
* Returns a two element array containing this map's key and value types in * Returns a two element array containing this map's key and value types in positions 0 and 1
* positions 0 and 1 respectively. * respectively.
*/ */
public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) { public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {
/* /*
@ -325,7 +326,7 @@ public final class $Gson$Types {
* extend Hashtable<Object, Object>. * extend Hashtable<Object, Object>.
*/ */
if (context == Properties.class) { if (context == Properties.class) {
return new Type[] { String.class, String.class }; // TODO: test subclasses of Properties! return new Type[] {String.class, String.class}; // TODO: test subclasses of Properties!
} }
Type mapType = getSupertype(context, contextRawType, Map.class); Type mapType = getSupertype(context, contextRawType, Map.class);
@ -334,7 +335,7 @@ public final class $Gson$Types {
ParameterizedType mapParameterizedType = (ParameterizedType) mapType; ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
return mapParameterizedType.getActualTypeArguments(); return mapParameterizedType.getActualTypeArguments();
} }
return new Type[] { Object.class, Object.class }; return new Type[] {Object.class, Object.class};
} }
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) { public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
@ -342,7 +343,10 @@ public final class $Gson$Types {
return resolve(context, contextRawType, toResolve, new HashMap<TypeVariable<?>, Type>()); return resolve(context, contextRawType, toResolve, new HashMap<TypeVariable<?>, Type>());
} }
private static Type resolve(Type context, Class<?> contextRawType, Type toResolve, private static Type resolve(
Type context,
Class<?> contextRawType,
Type toResolve,
Map<TypeVariable<?>, Type> visitedTypeVariables) { Map<TypeVariable<?>, Type> visitedTypeVariables) {
// this implementation is made a little more complicated in an attempt to avoid object-creation // this implementation is made a little more complicated in an attempt to avoid object-creation
TypeVariable<?> resolving = null; TypeVariable<?> resolving = null;
@ -369,19 +373,17 @@ public final class $Gson$Types {
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) { } else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
Class<?> original = (Class<?>) toResolve; Class<?> original = (Class<?>) toResolve;
Type componentType = original.getComponentType(); Type componentType = original.getComponentType();
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables); Type newComponentType =
toResolve = equal(componentType, newComponentType) resolve(context, contextRawType, componentType, visitedTypeVariables);
? original toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType);
: arrayOf(newComponentType);
break; break;
} else if (toResolve instanceof GenericArrayType) { } else if (toResolve instanceof GenericArrayType) {
GenericArrayType original = (GenericArrayType) toResolve; GenericArrayType original = (GenericArrayType) toResolve;
Type componentType = original.getGenericComponentType(); Type componentType = original.getGenericComponentType();
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables); Type newComponentType =
toResolve = equal(componentType, newComponentType) resolve(context, contextRawType, componentType, visitedTypeVariables);
? original toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType);
: arrayOf(newComponentType);
break; break;
} else if (toResolve instanceof ParameterizedType) { } else if (toResolve instanceof ParameterizedType) {
@ -392,7 +394,8 @@ public final class $Gson$Types {
Type[] args = original.getActualTypeArguments(); Type[] args = original.getActualTypeArguments();
for (int t = 0, length = args.length; t < length; t++) { for (int t = 0, length = args.length; t < length; t++) {
Type resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables); Type resolvedTypeArgument =
resolve(context, contextRawType, args[t], visitedTypeVariables);
if (!equal(resolvedTypeArgument, args[t])) { if (!equal(resolvedTypeArgument, args[t])) {
if (!changed) { if (!changed) {
args = args.clone(); args = args.clone();
@ -402,7 +405,8 @@ public final class $Gson$Types {
} }
} }
toResolve = changed toResolve =
changed
? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args) ? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
: original; : original;
break; break;
@ -413,13 +417,15 @@ public final class $Gson$Types {
Type[] originalUpperBound = original.getUpperBounds(); Type[] originalUpperBound = original.getUpperBounds();
if (originalLowerBound.length == 1) { if (originalLowerBound.length == 1) {
Type lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables); Type lowerBound =
resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
if (lowerBound != originalLowerBound[0]) { if (lowerBound != originalLowerBound[0]) {
toResolve = supertypeOf(lowerBound); toResolve = supertypeOf(lowerBound);
break; break;
} }
} else if (originalUpperBound.length == 1) { } else if (originalUpperBound.length == 1) {
Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables); Type upperBound =
resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
if (upperBound != originalUpperBound[0]) { if (upperBound != originalUpperBound[0]) {
toResolve = subtypeOf(upperBound); toResolve = subtypeOf(upperBound);
break; break;
@ -439,7 +445,8 @@ public final class $Gson$Types {
return toResolve; return toResolve;
} }
private static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) { private static Type resolveTypeVariable(
Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
Class<?> declaredByRaw = declaringClassOf(unknown); Class<?> declaredByRaw = declaringClassOf(unknown);
// we can't reduce this further // we can't reduce this further
@ -471,9 +478,7 @@ public final class $Gson$Types {
*/ */
private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) { private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
return genericDeclaration instanceof Class return genericDeclaration instanceof Class ? (Class<?>) genericDeclaration : null;
? (Class<?>) genericDeclaration
: null;
} }
static void checkNotPrimitive(Type type) { static void checkNotPrimitive(Type type) {
@ -503,8 +508,10 @@ public final class $Gson$Types {
private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable { private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {
@SuppressWarnings("serial") @SuppressWarnings("serial")
private final Type ownerType; private final Type ownerType;
@SuppressWarnings("serial") @SuppressWarnings("serial")
private final Type rawType; private final Type rawType;
@SuppressWarnings("serial") @SuppressWarnings("serial")
private final Type[] typeArguments; private final Type[] typeArguments;
@ -526,19 +533,23 @@ public final class $Gson$Types {
} }
} }
@Override public Type[] getActualTypeArguments() { @Override
public Type[] getActualTypeArguments() {
return typeArguments.clone(); return typeArguments.clone();
} }
@Override public Type getRawType() { @Override
public Type getRawType() {
return rawType; return rawType;
} }
@Override public Type getOwnerType() { @Override
public Type getOwnerType() {
return ownerType; return ownerType;
} }
@Override public boolean equals(Object other) { @Override
public boolean equals(Object other) {
return other instanceof ParameterizedType return other instanceof ParameterizedType
&& $Gson$Types.equals(this, (ParameterizedType) other); && $Gson$Types.equals(this, (ParameterizedType) other);
} }
@ -547,20 +558,23 @@ public final class $Gson$Types {
return o != null ? o.hashCode() : 0; return o != null ? o.hashCode() : 0;
} }
@Override public int hashCode() { @Override
return Arrays.hashCode(typeArguments) public int hashCode() {
^ rawType.hashCode() return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType);
^ hashCodeOrZero(ownerType);
} }
@Override public String toString() { @Override
public String toString() {
int length = typeArguments.length; int length = typeArguments.length;
if (length == 0) { if (length == 0) {
return typeToString(rawType); return typeToString(rawType);
} }
StringBuilder stringBuilder = new StringBuilder(30 * (length + 1)); StringBuilder stringBuilder = new StringBuilder(30 * (length + 1));
stringBuilder.append(typeToString(rawType)).append("<").append(typeToString(typeArguments[0])); stringBuilder
.append(typeToString(rawType))
.append("<")
.append(typeToString(typeArguments[0]));
for (int i = 1; i < length; i++) { for (int i = 1; i < length; i++) {
stringBuilder.append(", ").append(typeToString(typeArguments[i])); stringBuilder.append(", ").append(typeToString(typeArguments[i]));
} }
@ -579,20 +593,23 @@ public final class $Gson$Types {
this.componentType = canonicalize(componentType); this.componentType = canonicalize(componentType);
} }
@Override public Type getGenericComponentType() { @Override
public Type getGenericComponentType() {
return componentType; return componentType;
} }
@Override public boolean equals(Object o) { @Override
return o instanceof GenericArrayType public boolean equals(Object o) {
&& $Gson$Types.equals(this, (GenericArrayType) o); return o instanceof GenericArrayType && $Gson$Types.equals(this, (GenericArrayType) o);
} }
@Override public int hashCode() { @Override
public int hashCode() {
return componentType.hashCode(); return componentType.hashCode();
} }
@Override public String toString() { @Override
public String toString() {
return typeToString(componentType) + "[]"; return typeToString(componentType) + "[]";
} }
@ -600,14 +617,15 @@ public final class $Gson$Types {
} }
/** /**
* The WildcardType interface supports multiple upper bounds and multiple * The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only
* lower bounds. We only support what the target Java version supports - at most one * support what the target Java version supports - at most one bound, see also
* bound, see also https://bugs.openjdk.java.net/browse/JDK-8250660. If a lower bound * https://bugs.openjdk.java.net/browse/JDK-8250660. If a lower bound is set, the upper bound must
* is set, the upper bound must be Object.class. * be Object.class.
*/ */
private static final class WildcardTypeImpl implements WildcardType, Serializable { private static final class WildcardTypeImpl implements WildcardType, Serializable {
@SuppressWarnings("serial") @SuppressWarnings("serial")
private final Type upperBound; private final Type upperBound;
@SuppressWarnings("serial") @SuppressWarnings("serial")
private final Type lowerBound; private final Type lowerBound;
@ -630,26 +648,29 @@ public final class $Gson$Types {
} }
} }
@Override public Type[] getUpperBounds() { @Override
return new Type[] { upperBound }; public Type[] getUpperBounds() {
return new Type[] {upperBound};
} }
@Override public Type[] getLowerBounds() { @Override
return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY; public Type[] getLowerBounds() {
return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY;
} }
@Override public boolean equals(Object other) { @Override
return other instanceof WildcardType public boolean equals(Object other) {
&& $Gson$Types.equals(this, (WildcardType) other); return other instanceof WildcardType && $Gson$Types.equals(this, (WildcardType) other);
} }
@Override public int hashCode() { @Override
public int hashCode() {
// this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); // this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds());
return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode());
^ (31 + upperBound.hashCode());
} }
@Override public String toString() { @Override
public String toString() {
if (lowerBound != null) { if (lowerBound != null) {
return "? super " + typeToString(lowerBound); return "? super " + typeToString(lowerBound);
} else if (upperBound == Object.class) { } else if (upperBound == Object.class) {

View File

@ -47,23 +47,25 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListMap;
/** /** Returns a function that can construct an instance of a requested type. */
* Returns a function that can construct an instance of a requested type.
*/
public final class ConstructorConstructor { public final class ConstructorConstructor {
private final Map<Type, InstanceCreator<?>> instanceCreators; private final Map<Type, InstanceCreator<?>> instanceCreators;
private final boolean useJdkUnsafe; private final boolean useJdkUnsafe;
private final List<ReflectionAccessFilter> reflectionFilters; private final List<ReflectionAccessFilter> reflectionFilters;
public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators, boolean useJdkUnsafe, List<ReflectionAccessFilter> reflectionFilters) { public ConstructorConstructor(
Map<Type, InstanceCreator<?>> instanceCreators,
boolean useJdkUnsafe,
List<ReflectionAccessFilter> reflectionFilters) {
this.instanceCreators = instanceCreators; this.instanceCreators = instanceCreators;
this.useJdkUnsafe = useJdkUnsafe; this.useJdkUnsafe = useJdkUnsafe;
this.reflectionFilters = reflectionFilters; this.reflectionFilters = reflectionFilters;
} }
/** /**
* Check if the class can be instantiated by Unsafe allocator. If the instance has interface or abstract modifiers * Check if the class can be instantiated by Unsafe allocator. If the instance has interface or
* return an exception message. * abstract modifiers return an exception message.
*
* @param c instance of the class to be checked * @param c instance of the class to be checked
* @return if instantiable {@code null}, else a non-{@code null} exception message * @return if instantiable {@code null}, else a non-{@code null} exception message
*/ */
@ -71,7 +73,8 @@ public final class ConstructorConstructor {
int modifiers = c.getModifiers(); int modifiers = c.getModifiers();
if (Modifier.isInterface(modifiers)) { if (Modifier.isInterface(modifiers)) {
return "Interfaces can't be instantiated! Register an InstanceCreator" return "Interfaces can't be instantiated! Register an InstanceCreator"
+ " or a TypeAdapter for this type. Interface name: " + c.getName(); + " or a TypeAdapter for this type. Interface name: "
+ c.getName();
} }
if (Modifier.isAbstract(modifiers)) { if (Modifier.isAbstract(modifiers)) {
// R8 performs aggressive optimizations where it removes the default constructor of a class // R8 performs aggressive optimizations where it removes the default constructor of a class
@ -83,8 +86,10 @@ public final class ConstructorConstructor {
* still making the class abstract * still making the class abstract
*/ */
return "Abstract classes can't be instantiated! Adjust the R8 configuration or register" return "Abstract classes can't be instantiated! Adjust the R8 configuration or register"
+ " an InstanceCreator or a TypeAdapter for this type. Class name: " + c.getName() + " an InstanceCreator or a TypeAdapter for this type. Class name: "
+ "\nSee " + TroubleshootingGuide.createUrl("r8-abstract-class"); + c.getName()
+ "\nSee "
+ TroubleshootingGuide.createUrl("r8-abstract-class");
} }
return null; return null;
} }
@ -99,7 +104,8 @@ public final class ConstructorConstructor {
final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type); final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
if (typeCreator != null) { if (typeCreator != null) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return typeCreator.createInstance(type); return typeCreator.createInstance(type);
} }
}; };
@ -107,11 +113,11 @@ public final class ConstructorConstructor {
// Next try raw type match for instance creators // Next try raw type match for instance creators
@SuppressWarnings("unchecked") // types must agree @SuppressWarnings("unchecked") // types must agree
final InstanceCreator<T> rawTypeCreator = final InstanceCreator<T> rawTypeCreator = (InstanceCreator<T>) instanceCreators.get(rawType);
(InstanceCreator<T>) instanceCreators.get(rawType);
if (rawTypeCreator != null) { if (rawTypeCreator != null) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return rawTypeCreator.createInstance(type); return rawTypeCreator.createInstance(type);
} }
}; };
@ -125,7 +131,8 @@ public final class ConstructorConstructor {
return specialConstructor; return specialConstructor;
} }
FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, rawType); FilterResult filterResult =
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, rawType);
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult); ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult);
if (defaultConstructor != null) { if (defaultConstructor != null) {
return defaultConstructor; return defaultConstructor;
@ -141,7 +148,8 @@ public final class ConstructorConstructor {
final String exceptionMessage = checkInstantiable(rawType); final String exceptionMessage = checkInstantiable(rawType);
if (exceptionMessage != null) { if (exceptionMessage != null) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
throw new JsonIOException(exceptionMessage); throw new JsonIOException(exceptionMessage);
} }
}; };
@ -153,11 +161,15 @@ public final class ConstructorConstructor {
// finally try unsafe // finally try unsafe
return newUnsafeAllocator(rawType); return newUnsafeAllocator(rawType);
} else { } else {
final String message = "Unable to create instance of " + rawType + "; ReflectionAccessFilter" final String message =
+ " does not permit using reflection or Unsafe. Register an InstanceCreator or a TypeAdapter" "Unable to create instance of "
+ " for this type or adjust the access filter to allow using reflection."; + rawType
+ "; ReflectionAccessFilter does not permit using reflection or Unsafe. Register an"
+ " InstanceCreator or a TypeAdapter for this type or adjust the access filter to"
+ " allow using reflection.";
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
throw new JsonIOException(message); throw new JsonIOException(message);
} }
}; };
@ -165,17 +177,20 @@ public final class ConstructorConstructor {
} }
/** /**
* Creates constructors for special JDK collection types which do not have a public no-args constructor. * Creates constructors for special JDK collection types which do not have a public no-args
* constructor.
*/ */
private static <T> ObjectConstructor<T> newSpecialCollectionConstructor(final Type type, Class<? super T> rawType) { private static <T> ObjectConstructor<T> newSpecialCollectionConstructor(
final Type type, Class<? super T> rawType) {
if (EnumSet.class.isAssignableFrom(rawType)) { if (EnumSet.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
if (type instanceof ParameterizedType) { if (type instanceof ParameterizedType) {
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
if (elementType instanceof Class) { if (elementType instanceof Class) {
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
T set = (T) EnumSet.noneOf((Class)elementType); T set = (T) EnumSet.noneOf((Class) elementType);
return set; return set;
} else { } else {
throw new JsonIOException("Invalid EnumSet type: " + type.toString()); throw new JsonIOException("Invalid EnumSet type: " + type.toString());
@ -190,7 +205,8 @@ public final class ConstructorConstructor {
// and constructor parameter might have completely different meaning // and constructor parameter might have completely different meaning
else if (rawType == EnumMap.class) { else if (rawType == EnumMap.class) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
if (type instanceof ParameterizedType) { if (type instanceof ParameterizedType) {
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
if (elementType instanceof Class) { if (elementType instanceof Class) {
@ -210,7 +226,8 @@ public final class ConstructorConstructor {
return null; return null;
} }
private static <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType, FilterResult filterResult) { private static <T> ObjectConstructor<T> newDefaultConstructor(
Class<? super T> rawType, FilterResult filterResult) {
// Cannot invoke constructor of abstract class // Cannot invoke constructor of abstract class
if (Modifier.isAbstract(rawType.getModifiers())) { if (Modifier.isAbstract(rawType.getModifiers())) {
return null; return null;
@ -223,17 +240,25 @@ public final class ConstructorConstructor {
return null; return null;
} }
boolean canAccess = filterResult == FilterResult.ALLOW || (ReflectionAccessFilterHelper.canAccess(constructor, null) boolean canAccess =
// Be a bit more lenient here for BLOCK_ALL; if constructor is accessible and public then allow calling it filterResult == FilterResult.ALLOW
&& (filterResult != FilterResult.BLOCK_ALL || Modifier.isPublic(constructor.getModifiers()))); || (ReflectionAccessFilterHelper.canAccess(constructor, null)
// Be a bit more lenient here for BLOCK_ALL; if constructor is accessible and public
// then allow calling it
&& (filterResult != FilterResult.BLOCK_ALL
|| Modifier.isPublic(constructor.getModifiers())));
if (!canAccess) { if (!canAccess) {
final String message = "Unable to invoke no-args constructor of " + rawType + ";" final String message =
"Unable to invoke no-args constructor of "
+ rawType
+ ";"
+ " constructor is not accessible and ReflectionAccessFilter does not permit making" + " constructor is not accessible and ReflectionAccessFilter does not permit making"
+ " it accessible. Register an InstanceCreator or a TypeAdapter for this type, change" + " it accessible. Register an InstanceCreator or a TypeAdapter for this type, change"
+ " the visibility of the constructor or adjust the access filter."; + " the visibility of the constructor or adjust the access filter.";
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
throw new JsonIOException(message); throw new JsonIOException(message);
} }
}; };
@ -265,22 +290,31 @@ public final class ConstructorConstructor {
} }
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
try { try {
@SuppressWarnings("unchecked") // T is the same raw type as is requested @SuppressWarnings("unchecked") // T is the same raw type as is requested
T newInstance = (T) constructor.newInstance(); T newInstance = (T) constructor.newInstance();
return newInstance; return newInstance;
} }
// Note: InstantiationException should be impossible because check at start of method made sure // Note: InstantiationException should be impossible because check at start of method made
// that class is not abstract // sure that class is not abstract
catch (InstantiationException e) { catch (InstantiationException e) {
throw new RuntimeException("Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'" throw new RuntimeException(
+ " with no args", e); "Failed to invoke constructor '"
+ ReflectionHelper.constructorToString(constructor)
+ "'"
+ " with no args",
e);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
// TODO: don't wrap if cause is unchecked? // TODO: don't wrap if cause is unchecked?
// TODO: JsonParseException ? // TODO: JsonParseException ?
throw new RuntimeException("Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'" throw new RuntimeException(
+ " with no args", e.getCause()); "Failed to invoke constructor '"
+ ReflectionHelper.constructorToString(constructor)
+ "'"
+ " with no args",
e.getCause());
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e); throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
} }
@ -288,10 +322,7 @@ public final class ConstructorConstructor {
}; };
} }
/** /** Constructors for common interface types like Map and List and their subtypes. */
* Constructors for common interface types like Map and List and their
* subtypes.
*/
@SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is @SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
private static <T> ObjectConstructor<T> newDefaultImplementationConstructor( private static <T> ObjectConstructor<T> newDefaultImplementationConstructor(
final Type type, Class<? super T> rawType) { final Type type, Class<? super T> rawType) {
@ -307,25 +338,29 @@ public final class ConstructorConstructor {
if (Collection.class.isAssignableFrom(rawType)) { if (Collection.class.isAssignableFrom(rawType)) {
if (SortedSet.class.isAssignableFrom(rawType)) { if (SortedSet.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new TreeSet<>(); return (T) new TreeSet<>();
} }
}; };
} else if (Set.class.isAssignableFrom(rawType)) { } else if (Set.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new LinkedHashSet<>(); return (T) new LinkedHashSet<>();
} }
}; };
} else if (Queue.class.isAssignableFrom(rawType)) { } else if (Queue.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new ArrayDeque<>(); return (T) new ArrayDeque<>();
} }
}; };
} else { } else {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new ArrayList<>(); return (T) new ArrayList<>();
} }
}; };
@ -335,32 +370,38 @@ public final class ConstructorConstructor {
if (Map.class.isAssignableFrom(rawType)) { if (Map.class.isAssignableFrom(rawType)) {
if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) { if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new ConcurrentSkipListMap<>(); return (T) new ConcurrentSkipListMap<>();
} }
}; };
} else if (ConcurrentMap.class.isAssignableFrom(rawType)) { } else if (ConcurrentMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new ConcurrentHashMap<>(); return (T) new ConcurrentHashMap<>();
} }
}; };
} else if (SortedMap.class.isAssignableFrom(rawType)) { } else if (SortedMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new TreeMap<>(); return (T) new TreeMap<>();
} }
}; };
} else if (type instanceof ParameterizedType && !String.class.isAssignableFrom( } else if (type instanceof ParameterizedType
&& !String.class.isAssignableFrom(
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType())) { TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType())) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new LinkedHashMap<>(); return (T) new LinkedHashMap<>();
} }
}; };
} else { } else {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
return (T) new LinkedTreeMap<>(); return (T) new LinkedTreeMap<>();
} }
}; };
@ -373,41 +414,52 @@ public final class ConstructorConstructor {
private <T> ObjectConstructor<T> newUnsafeAllocator(final Class<? super T> rawType) { private <T> ObjectConstructor<T> newUnsafeAllocator(final Class<? super T> rawType) {
if (useJdkUnsafe) { if (useJdkUnsafe) {
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
try { try {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
T newInstance = (T) UnsafeAllocator.INSTANCE.newInstance(rawType); T newInstance = (T) UnsafeAllocator.INSTANCE.newInstance(rawType);
return newInstance; return newInstance;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(("Unable to create instance of " + rawType + "." throw new RuntimeException(
+ " Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args" ("Unable to create instance of "
+ " constructor may fix this problem."), e); + rawType
+ ". Registering an InstanceCreator or a TypeAdapter for this type, or adding a"
+ " no-args constructor may fix this problem."),
e);
} }
} }
}; };
} else { } else {
String exceptionMessage = "Unable to create instance of " + rawType + "; usage of JDK Unsafe" String exceptionMessage =
+ " is disabled. Registering an InstanceCreator or a TypeAdapter for this type, adding a no-args" "Unable to create instance of "
+ " constructor, or enabling usage of JDK Unsafe may fix this problem."; + rawType
+ "; usage of JDK Unsafe is disabled. Registering an InstanceCreator or a TypeAdapter"
+ " for this type, adding a no-args constructor, or enabling usage of JDK Unsafe may"
+ " fix this problem.";
// Check if R8 removed all constructors // Check if R8 removed all constructors
if (rawType.getDeclaredConstructors().length == 0) { if (rawType.getDeclaredConstructors().length == 0) {
// R8 with Unsafe disabled might not be common enough to warrant a separate Troubleshooting Guide entry // R8 with Unsafe disabled might not be common enough to warrant a separate Troubleshooting
exceptionMessage += " Or adjust your R8 configuration to keep the no-args constructor of the class."; // Guide entry
exceptionMessage +=
" Or adjust your R8 configuration to keep the no-args constructor of the class.";
} }
// Explicit final variable to allow usage in the anonymous class below // Explicit final variable to allow usage in the anonymous class below
final String exceptionMessageF = exceptionMessage; final String exceptionMessageF = exceptionMessage;
return new ObjectConstructor<T>() { return new ObjectConstructor<T>() {
@Override public T construct() { @Override
public T construct() {
throw new JsonIOException(exceptionMessageF); throw new JsonIOException(exceptionMessageF);
} }
}; };
} }
} }
@Override public String toString() { @Override
public String toString() {
return instanceCreators.toString(); return instanceCreators.toString();
} }
} }

View File

@ -35,14 +35,12 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* This class selects which fields and types to omit. It is configurable, * This class selects which fields and types to omit. It is configurable, supporting version
* supporting version attributes {@link Since} and {@link Until}, modifiers, * attributes {@link Since} and {@link Until}, modifiers, synthetic fields, anonymous and local
* synthetic fields, anonymous and local classes, inner classes, and fields with * classes, inner classes, and fields with the {@link Expose} annotation.
* the {@link Expose} annotation.
* *
* <p>This class is a type adapter factory; types that are excluded will be * <p>This class is a type adapter factory; types that are excluded will be adapted to null. It may
* adapted to null. It may delegate to another type adapter if only one * delegate to another type adapter if only one direction is excluded.
* direction is excluded.
* *
* @author Joel Leitch * @author Joel Leitch
* @author Jesse Wilson * @author Jesse Wilson
@ -58,7 +56,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
private List<ExclusionStrategy> serializationStrategies = Collections.emptyList(); private List<ExclusionStrategy> serializationStrategies = Collections.emptyList();
private List<ExclusionStrategy> deserializationStrategies = Collections.emptyList(); private List<ExclusionStrategy> deserializationStrategies = Collections.emptyList();
@Override protected Excluder clone() { @Override
protected Excluder clone() {
try { try {
return (Excluder) super.clone(); return (Excluder) super.clone();
} catch (CloneNotSupportedException e) { } catch (CloneNotSupportedException e) {
@ -93,8 +92,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
return result; return result;
} }
public Excluder withExclusionStrategy(ExclusionStrategy exclusionStrategy, public Excluder withExclusionStrategy(
boolean serialization, boolean deserialization) { ExclusionStrategy exclusionStrategy, boolean serialization, boolean deserialization) {
Excluder result = clone(); Excluder result = clone();
if (serialization) { if (serialization) {
result.serializationStrategies = new ArrayList<>(serializationStrategies); result.serializationStrategies = new ArrayList<>(serializationStrategies);
@ -107,7 +106,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
return result; return result;
} }
@Override public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) { @Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
Class<?> rawType = type.getRawType(); Class<?> rawType = type.getRawType();
boolean excludeClass = excludeClassChecks(rawType); boolean excludeClass = excludeClassChecks(rawType);
@ -122,7 +122,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
/** The delegate is lazily created because it may not be needed, and creating it may fail. */ /** The delegate is lazily created because it may not be needed, and creating it may fail. */
private TypeAdapter<T> delegate; private TypeAdapter<T> delegate;
@Override public T read(JsonReader in) throws IOException { @Override
public T read(JsonReader in) throws IOException {
if (skipDeserialize) { if (skipDeserialize) {
in.skipValue(); in.skipValue();
return null; return null;
@ -130,7 +131,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
return delegate().read(in); return delegate().read(in);
} }
@Override public void write(JsonWriter out, T value) throws IOException { @Override
public void write(JsonWriter out, T value) throws IOException {
if (skipSerialize) { if (skipSerialize) {
out.nullValue(); out.nullValue();
return; return;
@ -140,9 +142,7 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
private TypeAdapter<T> delegate() { private TypeAdapter<T> delegate() {
TypeAdapter<T> d = delegate; TypeAdapter<T> d = delegate;
return d != null return d != null ? d : (delegate = gson.getDelegateAdapter(Excluder.this, type));
? d
: (delegate = gson.getDelegateAdapter(Excluder.this, type));
} }
}; };
} }
@ -190,7 +190,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
} }
private boolean excludeClassChecks(Class<?> clazz) { private boolean excludeClassChecks(Class<?> clazz) {
if (version != Excluder.IGNORE_VERSIONS && !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) { if (version != Excluder.IGNORE_VERSIONS
&& !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) {
return true; return true;
} }
@ -202,8 +203,7 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
} }
public boolean excludeClass(Class<?> clazz, boolean serialize) { public boolean excludeClass(Class<?> clazz, boolean serialize) {
return excludeClassChecks(clazz) || return excludeClassChecks(clazz) || excludeClassInStrategy(clazz, serialize);
excludeClassInStrategy(clazz, serialize);
} }
private boolean excludeClassInStrategy(Class<?> clazz, boolean serialize) { private boolean excludeClassInStrategy(Class<?> clazz, boolean serialize) {
@ -217,7 +217,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
} }
private boolean isAnonymousOrNonStaticLocal(Class<?> clazz) { private boolean isAnonymousOrNonStaticLocal(Class<?> clazz) {
return !Enum.class.isAssignableFrom(clazz) && !isStatic(clazz) return !Enum.class.isAssignableFrom(clazz)
&& !isStatic(clazz)
&& (clazz.isAnonymousClass() || clazz.isLocalClass()); && (clazz.isAnonymousClass() || clazz.isLocalClass());
} }

View File

@ -16,12 +16,12 @@
package com.google.gson.internal; package com.google.gson.internal;
/** /** Utility to check the major Java version of the current JVM. */
* Utility to check the major Java version of the current JVM.
*/
public final class JavaVersion { public final class JavaVersion {
// Oracle defines naming conventions at http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html // Oracle defines naming conventions at
// However, many alternate implementations differ. For example, Debian used 9-debian as the version string // http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
// However, many alternate implementations differ. For example, Debian used 9-debian as the
// version string
private static final int majorJavaVersion = determineMajorJavaVersion(); private static final int majorJavaVersion = determineMajorJavaVersion();
@ -37,7 +37,7 @@ public final class JavaVersion {
version = extractBeginningInt(javaVersion); version = extractBeginningInt(javaVersion);
} }
if (version == -1) { if (version == -1) {
return 6; // Choose minimum supported JDK version as default return 6; // Choose a minimum supported JDK version as default
} }
return version; return version;
} }
@ -86,11 +86,12 @@ public final class JavaVersion {
/** /**
* Gets a boolean value depending if the application is running on Java 9 or later * Gets a boolean value depending if the application is running on Java 9 or later
* *
* @return {@code true} if the application is running on Java 9 or later; and {@code false} otherwise. * @return {@code true} if the application is running on Java 9 or later; and {@code false}
* otherwise.
*/ */
public static boolean isJava9OrLater() { public static boolean isJava9OrLater() {
return majorJavaVersion >= 9; return majorJavaVersion >= 9;
} }
private JavaVersion() { } private JavaVersion() {}
} }

View File

@ -19,14 +19,10 @@ package com.google.gson.internal;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import java.io.IOException; import java.io.IOException;
/** /** Internal-only APIs of JsonReader available only to other classes in Gson. */
* Internal-only APIs of JsonReader available only to other classes in Gson.
*/
public abstract class JsonReaderInternalAccess { public abstract class JsonReaderInternalAccess {
public static JsonReaderInternalAccess INSTANCE; public static JsonReaderInternalAccess INSTANCE;
/** /** Changes the type of the current property name token to a string value. */
* Changes the type of the current property name token to a string value.
*/
public abstract void promoteNameToValue(JsonReader reader) throws IOException; public abstract void promoteNameToValue(JsonReader reader) throws IOException;
} }

View File

@ -30,7 +30,9 @@ import java.math.BigDecimal;
public final class LazilyParsedNumber extends Number { public final class LazilyParsedNumber extends Number {
private final String value; private final String value;
/** @param value must not be null */ /**
* @param value must not be null
*/
public LazilyParsedNumber(String value) { public LazilyParsedNumber(String value) {
this.value = value; this.value = value;
} }
@ -77,16 +79,16 @@ public final class LazilyParsedNumber extends Number {
} }
/** /**
* If somebody is unlucky enough to have to serialize one of these, serialize * If somebody is unlucky enough to have to serialize one of these, serialize it as a BigDecimal
* it as a BigDecimal so that they won't need Gson on the other side to * so that they won't need Gson on the other side to deserialize it.
* deserialize it.
*/ */
private Object writeReplace() throws ObjectStreamException { private Object writeReplace() throws ObjectStreamException {
return asBigDecimal(); return asBigDecimal();
} }
private void readObject(ObjectInputStream in) throws IOException { private void readObject(ObjectInputStream in) throws IOException {
// Don't permit directly deserializing this class; writeReplace() should have written a replacement // Don't permit directly deserializing this class; writeReplace() should have written a
// replacement
throw new InvalidObjectException("Deserialization is unsupported"); throw new InvalidObjectException("Deserialization is unsupported");
} }

View File

@ -30,21 +30,23 @@ import java.util.ConcurrentModificationException;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
/** /**
* A map of comparable keys to values. Unlike {@code TreeMap}, this class uses * A map of comparable keys to values. Unlike {@code TreeMap}, this class uses insertion order for
* insertion order for iteration order. Comparison order is only used as an * iteration order. Comparison order is only used as an optimization for efficient insertion and
* optimization for efficient insertion and removal. * removal.
* *
* <p>This implementation was derived from Android 4.1's TreeMap class. * <p>This implementation was derived from Android 4.1's TreeMap class.
*/ */
@SuppressWarnings("serial") // ignore warning about missing serialVersionUID @SuppressWarnings("serial") // ignore warning about missing serialVersionUID
public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Serializable { public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Serializable {
@SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable<Comparable<Comparable<...>>> @SuppressWarnings({"unchecked", "rawtypes"}) // to avoid Comparable<Comparable<Comparable<...>>>
private static final Comparator<Comparable> NATURAL_ORDER = new Comparator<Comparable>() { private static final Comparator<Comparable> NATURAL_ORDER =
@Override public int compare(Comparable a, Comparable b) { new Comparator<Comparable>() {
@Override
public int compare(Comparable a, Comparable b) {
return a.compareTo(b); return a.compareTo(b);
} }
}; };
@ -59,8 +61,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
final Node<K, V> header; final Node<K, V> header;
/** /**
* Create a natural order, empty tree map whose keys must be mutually * Create a natural order, empty tree map whose keys must be mutually comparable and non-null, and
* comparable and non-null, and whose values can be {@code null}. * whose values can be {@code null}.
*/ */
@SuppressWarnings("unchecked") // unsafe! this assumes K is comparable @SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
public LinkedTreeMap() { public LinkedTreeMap() {
@ -68,8 +70,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
} }
/** /**
* Create a natural order, empty tree map whose keys must be mutually * Create a natural order, empty tree map whose keys must be mutually comparable and non-null.
* comparable and non-null.
* *
* @param allowNullValues whether {@code null} is allowed as entry value * @param allowNullValues whether {@code null} is allowed as entry value
*/ */
@ -79,37 +80,40 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
} }
/** /**
* Create a tree map ordered by {@code comparator}. This map's keys may only * Create a tree map ordered by {@code comparator}. This map's keys may only be null if {@code
* be null if {@code comparator} permits. * comparator} permits.
* *
* @param comparator the comparator to order elements with, or {@code null} to * @param comparator the comparator to order elements with, or {@code null} to use the natural
* use the natural ordering. * ordering.
* @param allowNullValues whether {@code null} is allowed as entry value * @param allowNullValues whether {@code null} is allowed as entry value
*/ */
@SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable // unsafe! if comparator is null, this assumes K is comparable
@SuppressWarnings({"unchecked", "rawtypes"})
public LinkedTreeMap(Comparator<? super K> comparator, boolean allowNullValues) { public LinkedTreeMap(Comparator<? super K> comparator, boolean allowNullValues) {
this.comparator = comparator != null this.comparator = comparator != null ? comparator : (Comparator) NATURAL_ORDER;
? comparator
: (Comparator) NATURAL_ORDER;
this.allowNullValues = allowNullValues; this.allowNullValues = allowNullValues;
this.header = new Node<>(allowNullValues); this.header = new Node<>(allowNullValues);
} }
@Override public int size() { @Override
public int size() {
return size; return size;
} }
@Override public V get(Object key) { @Override
public V get(Object key) {
Node<K, V> node = findByObject(key); Node<K, V> node = findByObject(key);
return node != null ? node.value : null; return node != null ? node.value : null;
} }
@Override public boolean containsKey(Object key) { @Override
public boolean containsKey(Object key) {
return findByObject(key) != null; return findByObject(key) != null;
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public V put(K key, V value) { @Override
public V put(K key, V value) {
if (key == null) { if (key == null) {
throw new NullPointerException("key == null"); throw new NullPointerException("key == null");
} }
@ -122,7 +126,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
return result; return result;
} }
@Override public void clear() { @Override
public void clear() {
root = null; root = null;
size = 0; size = 0;
modCount++; modCount++;
@ -132,7 +137,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
header.next = header.prev = header; header.next = header.prev = header;
} }
@Override public V remove(Object key) { @Override
public V remove(Object key) {
Node<K, V> node = removeInternalByKey(key); Node<K, V> node = removeInternalByKey(key);
return node != null ? node.value : null; return node != null ? node.value : null;
} }
@ -140,8 +146,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
/** /**
* Returns the node at or adjacent to the given key, creating it if requested. * Returns the node at or adjacent to the given key, creating it if requested.
* *
* @throws ClassCastException if {@code key} and the tree's keys aren't * @throws ClassCastException if {@code key} and the tree's keys aren't mutually comparable.
* mutually comparable.
*/ */
Node<K, V> find(K key, boolean create) { Node<K, V> find(K key, boolean create) {
Comparator<? super K> comparator = this.comparator; Comparator<? super K> comparator = this.comparator;
@ -151,12 +156,12 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
if (nearest != null) { if (nearest != null) {
// Micro-optimization: avoid polymorphic calls to Comparator.compare(). // Micro-optimization: avoid polymorphic calls to Comparator.compare().
@SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble. @SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble.
Comparable<Object> comparableKey = (comparator == NATURAL_ORDER) Comparable<Object> comparableKey =
? (Comparable<Object>) key (comparator == NATURAL_ORDER) ? (Comparable<Object>) key : null;
: null;
while (true) { while (true) {
comparison = (comparableKey != null) comparison =
(comparableKey != null)
? comparableKey.compareTo(nearest.key) ? comparableKey.compareTo(nearest.key)
: comparator.compare(key, nearest.key); : comparator.compare(key, nearest.key);
@ -215,13 +220,12 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
} }
/** /**
* Returns this map's entry that has the same key and value as {@code * Returns this map's entry that has the same key and value as {@code entry}, or null if this map
* entry}, or null if this map has no such entry. * has no such entry.
* *
* <p>This method uses the comparator for key equality rather than {@code * <p>This method uses the comparator for key equality rather than {@code equals}. If this map's
* equals}. If this map's comparator isn't consistent with equals (such as * comparator isn't consistent with equals (such as {@code String.CASE_INSENSITIVE_ORDER}), then
* {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code * {@code remove()} and {@code contains()} will violate the collections API.
* contains()} will violate the collections API.
*/ */
Node<K, V> findByEntry(Entry<?, ?> entry) { Node<K, V> findByEntry(Entry<?, ?> entry) {
Node<K, V> mine = findByObject(entry.getKey()); Node<K, V> mine = findByObject(entry.getKey());
@ -234,8 +238,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
} }
/** /**
* Removes {@code node} from this tree, rearranging the tree's structure as * Removes {@code node} from this tree, rearranging the tree's structure as necessary.
* necessary.
* *
* @param unlink true to also unlink this node from the iteration linked list. * @param unlink true to also unlink this node from the iteration linked list.
*/ */
@ -327,11 +330,10 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
} }
/** /**
* Rebalances the tree by making any AVL rotations necessary between the * Rebalances the tree by making any AVL rotations necessary between the newly-unbalanced node and
* newly-unbalanced node and the tree's root. * the tree's root.
* *
* @param insert true if the node was unbalanced by an insert; false if it * @param insert true if the node was unbalanced by an insert; false if it was by a removal.
* was by a removal.
*/ */
private void rebalance(Node<K, V> unbalanced, boolean insert) { private void rebalance(Node<K, V> unbalanced, boolean insert) {
for (Node<K, V> node = unbalanced; node != null; node = node.parent) { for (Node<K, V> node = unbalanced; node != null; node = node.parent) {
@ -393,9 +395,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
} }
} }
/** /** Rotates the subtree so that its root's right child is the new root. */
* Rotates the subtree so that its root's right child is the new root.
*/
private void rotateLeft(Node<K, V> root) { private void rotateLeft(Node<K, V> root) {
Node<K, V> left = root.left; Node<K, V> left = root.left;
Node<K, V> pivot = root.right; Node<K, V> pivot = root.right;
@ -415,15 +415,12 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
root.parent = pivot; root.parent = pivot;
// fix heights // fix heights
root.height = Math.max(left != null ? left.height : 0, root.height =
pivotLeft != null ? pivotLeft.height : 0) + 1; Math.max(left != null ? left.height : 0, pivotLeft != null ? pivotLeft.height : 0) + 1;
pivot.height = Math.max(root.height, pivot.height = Math.max(root.height, pivotRight != null ? pivotRight.height : 0) + 1;
pivotRight != null ? pivotRight.height : 0) + 1;
} }
/** /** Rotates the subtree so that its root's left child is the new root. */
* Rotates the subtree so that its root's left child is the new root.
*/
private void rotateRight(Node<K, V> root) { private void rotateRight(Node<K, V> root) {
Node<K, V> pivot = root.left; Node<K, V> pivot = root.left;
Node<K, V> right = root.right; Node<K, V> right = root.right;
@ -443,21 +440,22 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
root.parent = pivot; root.parent = pivot;
// fixup heights // fixup heights
root.height = Math.max(right != null ? right.height : 0, root.height =
pivotRight != null ? pivotRight.height : 0) + 1; Math.max(right != null ? right.height : 0, pivotRight != null ? pivotRight.height : 0) + 1;
pivot.height = Math.max(root.height, pivot.height = Math.max(root.height, pivotLeft != null ? pivotLeft.height : 0) + 1;
pivotLeft != null ? pivotLeft.height : 0) + 1;
} }
private EntrySet entrySet; private EntrySet entrySet;
private KeySet keySet; private KeySet keySet;
@Override public Set<Entry<K, V>> entrySet() { @Override
public Set<Entry<K, V>> entrySet() {
EntrySet result = entrySet; EntrySet result = entrySet;
return result != null ? result : (entrySet = new EntrySet()); return result != null ? result : (entrySet = new EntrySet());
} }
@Override public Set<K> keySet() { @Override
public Set<K> keySet() {
KeySet result = keySet; KeySet result = keySet;
return result != null ? result : (keySet = new KeySet()); return result != null ? result : (keySet = new KeySet());
} }
@ -492,15 +490,18 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
next.prev = this; next.prev = this;
} }
@Override public K getKey() { @Override
public K getKey() {
return key; return key;
} }
@Override public V getValue() { @Override
public V getValue() {
return value; return value;
} }
@Override public V setValue(V value) { @Override
public V setValue(V value) {
if (value == null && !allowNullValue) { if (value == null && !allowNullValue) {
throw new NullPointerException("value == null"); throw new NullPointerException("value == null");
} }
@ -509,7 +510,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
return oldValue; return oldValue;
} }
@Override public boolean equals(Object o) { @Override
public boolean equals(Object o) {
if (o instanceof Entry) { if (o instanceof Entry) {
Entry<?, ?> other = (Entry<?, ?>) o; Entry<?, ?> other = (Entry<?, ?>) o;
return (key == null ? other.getKey() == null : key.equals(other.getKey())) return (key == null ? other.getKey() == null : key.equals(other.getKey()))
@ -518,18 +520,17 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
return false; return false;
} }
@Override public int hashCode() { @Override
return (key == null ? 0 : key.hashCode()) public int hashCode() {
^ (value == null ? 0 : value.hashCode()); return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
} }
@Override public String toString() { @Override
public String toString() {
return key + "=" + value; return key + "=" + value;
} }
/** /** Returns the first node in this subtree. */
* Returns the first node in this subtree.
*/
public Node<K, V> first() { public Node<K, V> first() {
Node<K, V> node = this; Node<K, V> node = this;
Node<K, V> child = node.left; Node<K, V> child = node.left;
@ -540,9 +541,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
return node; return node;
} }
/** /** Returns the last node in this subtree. */
* Returns the last node in this subtree.
*/
public Node<K, V> last() { public Node<K, V> last() {
Node<K, V> node = this; Node<K, V> node = this;
Node<K, V> child = node.right; Node<K, V> child = node.right;
@ -559,8 +558,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
Node<K, V> lastReturned = null; Node<K, V> lastReturned = null;
int expectedModCount = modCount; int expectedModCount = modCount;
LinkedTreeMapIterator() { LinkedTreeMapIterator() {}
}
@Override @Override
@SuppressWarnings("ReferenceEquality") @SuppressWarnings("ReferenceEquality")
@ -581,7 +579,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
return lastReturned = e; return lastReturned = e;
} }
@Override public final void remove() { @Override
public final void remove() {
if (lastReturned == null) { if (lastReturned == null) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
@ -592,23 +591,28 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
} }
class EntrySet extends AbstractSet<Entry<K, V>> { class EntrySet extends AbstractSet<Entry<K, V>> {
@Override public int size() { @Override
public int size() {
return size; return size;
} }
@Override public Iterator<Entry<K, V>> iterator() { @Override
public Iterator<Entry<K, V>> iterator() {
return new LinkedTreeMapIterator<Entry<K, V>>() { return new LinkedTreeMapIterator<Entry<K, V>>() {
@Override public Entry<K, V> next() { @Override
public Entry<K, V> next() {
return nextNode(); return nextNode();
} }
}; };
} }
@Override public boolean contains(Object o) { @Override
public boolean contains(Object o) {
return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null; return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null;
} }
@Override public boolean remove(Object o) { @Override
public boolean remove(Object o) {
if (!(o instanceof Entry)) { if (!(o instanceof Entry)) {
return false; return false;
} }
@ -621,49 +625,56 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
return true; return true;
} }
@Override public void clear() { @Override
public void clear() {
LinkedTreeMap.this.clear(); LinkedTreeMap.this.clear();
} }
} }
final class KeySet extends AbstractSet<K> { final class KeySet extends AbstractSet<K> {
@Override public int size() { @Override
public int size() {
return size; return size;
} }
@Override public Iterator<K> iterator() { @Override
public Iterator<K> iterator() {
return new LinkedTreeMapIterator<K>() { return new LinkedTreeMapIterator<K>() {
@Override public K next() { @Override
public K next() {
return nextNode().key; return nextNode().key;
} }
}; };
} }
@Override public boolean contains(Object o) { @Override
public boolean contains(Object o) {
return containsKey(o); return containsKey(o);
} }
@Override public boolean remove(Object key) { @Override
public boolean remove(Object key) {
return removeInternalByKey(key) != null; return removeInternalByKey(key) != null;
} }
@Override public void clear() { @Override
public void clear() {
LinkedTreeMap.this.clear(); LinkedTreeMap.this.clear();
} }
} }
/** /**
* If somebody is unlucky enough to have to serialize one of these, serialize * If somebody is unlucky enough to have to serialize one of these, serialize it as a
* it as a LinkedHashMap so that they won't need Gson on the other side to * LinkedHashMap so that they won't need Gson on the other side to deserialize it. Using
* deserialize it. Using serialization defeats our DoS defence, so most apps * serialization defeats our DoS defence, so most apps shouldn't use it.
* shouldn't use it.
*/ */
private Object writeReplace() throws ObjectStreamException { private Object writeReplace() throws ObjectStreamException {
return new LinkedHashMap<>(this); return new LinkedHashMap<>(this);
} }
private void readObject(ObjectInputStream in) throws IOException { private void readObject(ObjectInputStream in) throws IOException {
// Don't permit directly deserializing this class; writeReplace() should have written a replacement // Don't permit directly deserializing this class; writeReplace() should have written a
// replacement
throw new InvalidObjectException("Deserialization is unsupported"); throw new InvalidObjectException("Deserialization is unsupported");
} }
} }

View File

@ -24,10 +24,9 @@ import java.util.Objects;
import java.util.RandomAccess; import java.util.RandomAccess;
/** /**
* {@link List} which wraps another {@code List} but prevents insertion of * {@link List} which wraps another {@code List} but prevents insertion of {@code null} elements.
* {@code null} elements. Methods which only perform checks with the element * Methods which only perform checks with the element argument (e.g. {@link #contains(Object)}) do
* argument (e.g. {@link #contains(Object)}) do not throw exceptions for * not throw exceptions for {@code null} arguments.
* {@code null} arguments.
*/ */
public class NonNullElementWrapperList<E> extends AbstractList<E> implements RandomAccess { public class NonNullElementWrapperList<E> extends AbstractList<E> implements RandomAccess {
// Explicitly specify ArrayList as type to guarantee that delegate implements RandomAccess // Explicitly specify ArrayList as type to guarantee that delegate implements RandomAccess
@ -38,11 +37,13 @@ public class NonNullElementWrapperList<E> extends AbstractList<E> implements Ran
this.delegate = Objects.requireNonNull(delegate); this.delegate = Objects.requireNonNull(delegate);
} }
@Override public E get(int index) { @Override
public E get(int index) {
return delegate.get(index); return delegate.get(index);
} }
@Override public int size() { @Override
public int size() {
return delegate.size(); return delegate.size();
} }
@ -53,61 +54,75 @@ public class NonNullElementWrapperList<E> extends AbstractList<E> implements Ran
return element; return element;
} }
@Override public E set(int index, E element) { @Override
public E set(int index, E element) {
return delegate.set(index, nonNull(element)); return delegate.set(index, nonNull(element));
} }
@Override public void add(int index, E element) { @Override
public void add(int index, E element) {
delegate.add(index, nonNull(element)); delegate.add(index, nonNull(element));
} }
@Override public E remove(int index) { @Override
public E remove(int index) {
return delegate.remove(index); return delegate.remove(index);
} }
/* The following methods are overridden because their default implementation is inefficient */ /* The following methods are overridden because their default implementation is inefficient */
@Override public void clear() { @Override
public void clear() {
delegate.clear(); delegate.clear();
} }
@Override public boolean remove(Object o) { @Override
public boolean remove(Object o) {
return delegate.remove(o); return delegate.remove(o);
} }
@Override public boolean removeAll(Collection<?> c) { @Override
public boolean removeAll(Collection<?> c) {
return delegate.removeAll(c); return delegate.removeAll(c);
} }
@Override public boolean retainAll(Collection<?> c) { @Override
public boolean retainAll(Collection<?> c) {
return delegate.retainAll(c); return delegate.retainAll(c);
} }
@Override public boolean contains(Object o) { @Override
public boolean contains(Object o) {
return delegate.contains(o); return delegate.contains(o);
} }
@Override public int indexOf(Object o) { @Override
public int indexOf(Object o) {
return delegate.indexOf(o); return delegate.indexOf(o);
} }
@Override public int lastIndexOf(Object o) { @Override
public int lastIndexOf(Object o) {
return delegate.lastIndexOf(o); return delegate.lastIndexOf(o);
} }
@Override public Object[] toArray() { @Override
public Object[] toArray() {
return delegate.toArray(); return delegate.toArray();
} }
@Override public <T> T[] toArray(T[] a) { @Override
public <T> T[] toArray(T[] a) {
return delegate.toArray(a); return delegate.toArray(a);
} }
@Override public boolean equals(Object o) { @Override
public boolean equals(Object o) {
return delegate.equals(o); return delegate.equals(o);
} }
@Override public int hashCode() { @Override
public int hashCode() {
return delegate.hashCode(); return delegate.hashCode();
} }

View File

@ -4,12 +4,11 @@ import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
/** /**
* This class enforces limits on numbers parsed from JSON to avoid potential performance * This class enforces limits on numbers parsed from JSON to avoid potential performance problems
* problems when extremely large numbers are used. * when extremely large numbers are used.
*/ */
public class NumberLimits { public class NumberLimits {
private NumberLimits() { private NumberLimits() {}
}
private static final int MAX_NUMBER_STRING_LENGTH = 10_000; private static final int MAX_NUMBER_STRING_LENGTH = 10_000;

View File

@ -17,17 +17,15 @@
package com.google.gson.internal; package com.google.gson.internal;
/** /**
* Defines a generic object construction factory. The purpose of this class * Defines a generic object construction factory. The purpose of this class is to construct a
* is to construct a default instance of a class that can be used for object * default instance of a class that can be used for object navigation while deserialization from its
* navigation while deserialization from its JSON representation. * JSON representation.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*/ */
public interface ObjectConstructor<T> { public interface ObjectConstructor<T> {
/** /** Returns a new instance. */
* Returns a new instance.
*/
public T construct(); public T construct();
} }

View File

@ -19,24 +19,24 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Locale; import java.util.Locale;
/** /** Provides DateFormats for US locale with patterns which were the default ones before Java 9. */
* Provides DateFormats for US locale with patterns which were the default ones before Java 9.
*/
public class PreJava9DateFormatProvider { public class PreJava9DateFormatProvider {
/** /**
* Returns the same DateFormat as {@code DateFormat.getDateInstance(style, Locale.US)} in Java 8 or below. * Returns the same DateFormat as {@code DateFormat.getDateInstance(style, Locale.US)} in Java 8
* or below.
*/ */
public static DateFormat getUSDateFormat(int style) { public static DateFormat getUSDateFormat(int style) {
return new SimpleDateFormat(getDateFormatPattern(style), Locale.US); return new SimpleDateFormat(getDateFormatPattern(style), Locale.US);
} }
/** /**
* Returns the same DateFormat as {@code DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US)} * Returns the same DateFormat as {@code DateFormat.getDateTimeInstance(dateStyle, timeStyle,
* in Java 8 or below. * Locale.US)} in Java 8 or below.
*/ */
public static DateFormat getUSDateTimeFormat(int dateStyle, int timeStyle) { public static DateFormat getUSDateTimeFormat(int dateStyle, int timeStyle) {
String pattern = getDatePartOfDateTimePattern(dateStyle) + " " + getTimePartOfDateTimePattern(timeStyle); String pattern =
getDatePartOfDateTimePattern(dateStyle) + " " + getTimePartOfDateTimePattern(timeStyle);
return new SimpleDateFormat(pattern, Locale.US); return new SimpleDateFormat(pattern, Locale.US);
} }

View File

@ -19,24 +19,22 @@ package com.google.gson.internal;
import java.lang.reflect.Type; import java.lang.reflect.Type;
/** /**
* Contains static utility methods pertaining to primitive types and their * Contains static utility methods pertaining to primitive types and their corresponding wrapper
* corresponding wrapper types. * types.
* *
* @author Kevin Bourrillion * @author Kevin Bourrillion
*/ */
public final class Primitives { public final class Primitives {
private Primitives() {} private Primitives() {}
/** /** Returns true if this type is a primitive. */
* Returns true if this type is a primitive.
*/
public static boolean isPrimitive(Type type) { public static boolean isPrimitive(Type type) {
return type instanceof Class<?> && ((Class<?>) type).isPrimitive(); return type instanceof Class<?> && ((Class<?>) type).isPrimitive();
} }
/** /**
* Returns {@code true} if {@code type} is one of the nine * Returns {@code true} if {@code type} is one of the nine primitive-wrapper types, such as {@link
* primitive-wrapper types, such as {@link Integer}. * Integer}.
* *
* @see Class#isPrimitive * @see Class#isPrimitive
*/ */
@ -53,8 +51,9 @@ public final class Primitives {
} }
/** /**
* Returns the corresponding wrapper type of {@code type} if it is a primitive * Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise
* type; otherwise returns {@code type} itself. Idempotent. * returns {@code type} itself. Idempotent.
*
* <pre> * <pre>
* wrap(int.class) == Integer.class * wrap(int.class) == Integer.class
* wrap(Integer.class) == Integer.class * wrap(Integer.class) == Integer.class
@ -76,8 +75,9 @@ public final class Primitives {
} }
/** /**
* Returns the corresponding primitive type of {@code type} if it is a * Returns the corresponding primitive type of {@code type} if it is a wrapper type; otherwise
* wrapper type; otherwise returns {@code type} itself. Idempotent. * returns {@code type} itself. Idempotent.
*
* <pre> * <pre>
* unwrap(Integer.class) == int.class * unwrap(Integer.class) == int.class
* unwrap(int.class) == int.class * unwrap(int.class) == int.class

View File

@ -16,21 +16,19 @@
package com.google.gson.internal; package com.google.gson.internal;
import com.google.gson.ReflectionAccessFilter;
import com.google.gson.ReflectionAccessFilter.FilterResult;
import java.lang.reflect.AccessibleObject; import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import com.google.gson.ReflectionAccessFilter; /** Internal helper class for {@link ReflectionAccessFilter}. */
import com.google.gson.ReflectionAccessFilter.FilterResult;
/**
* Internal helper class for {@link ReflectionAccessFilter}.
*/
public class ReflectionAccessFilterHelper { public class ReflectionAccessFilterHelper {
private ReflectionAccessFilterHelper() { } private ReflectionAccessFilterHelper() {}
// Platform type detection is based on Moshi's Util.isPlatformType(Class) // Platform type detection is based on Moshi's Util.isPlatformType(Class)
// See https://github.com/square/moshi/blob/3c108919ee1cce88a433ffda04eeeddc0341eae7/moshi/src/main/java/com/squareup/moshi/internal/Util.java#L141 // See
// https://github.com/square/moshi/blob/3c108919ee1cce88a433ffda04eeeddc0341eae7/moshi/src/main/java/com/squareup/moshi/internal/Util.java#L141
public static boolean isJavaType(Class<?> c) { public static boolean isJavaType(Class<?> c) {
return isJavaType(c.getName()); return isJavaType(c.getName());
@ -59,11 +57,12 @@ public class ReflectionAccessFilterHelper {
} }
/** /**
* Gets the result of applying all filters until the first one returns a result * Gets the result of applying all filters until the first one returns a result other than {@link
* other than {@link FilterResult#INDECISIVE}, or {@link FilterResult#ALLOW} if * FilterResult#INDECISIVE}, or {@link FilterResult#ALLOW} if the list of filters is empty or all
* the list of filters is empty or all returned {@code INDECISIVE}. * returned {@code INDECISIVE}.
*/ */
public static FilterResult getFilterResult(List<ReflectionAccessFilter> reflectionFilters, Class<?> c) { public static FilterResult getFilterResult(
List<ReflectionAccessFilter> reflectionFilters, Class<?> c) {
for (ReflectionAccessFilter filter : reflectionFilters) { for (ReflectionAccessFilter filter : reflectionFilters) {
FilterResult result = filter.check(c); FilterResult result = filter.check(c);
if (result != FilterResult.INDECISIVE) { if (result != FilterResult.INDECISIVE) {
@ -73,23 +72,25 @@ public class ReflectionAccessFilterHelper {
return FilterResult.ALLOW; return FilterResult.ALLOW;
} }
/** /** See {@link AccessibleObject#canAccess(Object)} (Java >= 9) */
* See {@link AccessibleObject#canAccess(Object)} (Java >= 9)
*/
public static boolean canAccess(AccessibleObject accessibleObject, Object object) { public static boolean canAccess(AccessibleObject accessibleObject, Object object) {
return AccessChecker.INSTANCE.canAccess(accessibleObject, object); return AccessChecker.INSTANCE.canAccess(accessibleObject, object);
} }
private static abstract class AccessChecker { private abstract static class AccessChecker {
public static final AccessChecker INSTANCE; public static final AccessChecker INSTANCE;
static { static {
AccessChecker accessChecker = null; AccessChecker accessChecker = null;
// TODO: Ideally should use Multi-Release JAR for this version specific code // TODO: Ideally should use Multi-Release JAR for this version specific code
if (JavaVersion.isJava9OrLater()) { if (JavaVersion.isJava9OrLater()) {
try { try {
final Method canAccessMethod = AccessibleObject.class.getDeclaredMethod("canAccess", Object.class); final Method canAccessMethod =
accessChecker = new AccessChecker() { AccessibleObject.class.getDeclaredMethod("canAccess", Object.class);
@Override public boolean canAccess(AccessibleObject accessibleObject, Object object) { accessChecker =
new AccessChecker() {
@Override
public boolean canAccess(AccessibleObject accessibleObject, Object object) {
try { try {
return (Boolean) canAccessMethod.invoke(accessibleObject, object); return (Boolean) canAccessMethod.invoke(accessibleObject, object);
} catch (Exception e) { } catch (Exception e) {
@ -103,8 +104,10 @@ public class ReflectionAccessFilterHelper {
} }
if (accessChecker == null) { if (accessChecker == null) {
accessChecker = new AccessChecker() { accessChecker =
@Override public boolean canAccess(AccessibleObject accessibleObject, Object object) { new AccessChecker() {
@Override
public boolean canAccess(AccessibleObject accessibleObject, Object object) {
// Cannot determine whether object can be accessed, so assume it can be accessed // Cannot determine whether object can be accessed, so assume it can be accessed
return true; return true;
} }

View File

@ -31,17 +31,13 @@ import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.Objects; import java.util.Objects;
/** /** Reads and writes GSON parse trees over streams. */
* Reads and writes GSON parse trees over streams.
*/
public final class Streams { public final class Streams {
private Streams() { private Streams() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/** /** Takes a reader in any state and returns the next value as a JsonElement. */
* Takes a reader in any state and returns the next value as a JsonElement.
*/
public static JsonElement parse(JsonReader reader) throws JsonParseException { public static JsonElement parse(JsonReader reader) throws JsonParseException {
boolean isEmpty = true; boolean isEmpty = true;
try { try {
@ -67,9 +63,7 @@ public final class Streams {
} }
} }
/** /** Writes the JSON element to the writer, recursively. */
* Writes the JSON element to the writer, recursively.
*/
public static void write(JsonElement element, JsonWriter writer) throws IOException { public static void write(JsonElement element, JsonWriter writer) throws IOException {
TypeAdapters.JSON_ELEMENT.write(writer, element); TypeAdapters.JSON_ELEMENT.write(writer, element);
} }
@ -78,10 +72,7 @@ public final class Streams {
return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable); return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
} }
/** /** Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer} is used. */
* Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer}
* is used.
*/
private static final class AppendableWriter extends Writer { private static final class AppendableWriter extends Writer {
private final Appendable appendable; private final Appendable appendable;
private final CurrentWrite currentWrite = new CurrentWrite(); private final CurrentWrite currentWrite = new CurrentWrite();
@ -90,40 +81,46 @@ public final class Streams {
this.appendable = appendable; this.appendable = appendable;
} }
@Override public void write(char[] chars, int offset, int length) throws IOException { @Override
public void write(char[] chars, int offset, int length) throws IOException {
currentWrite.setChars(chars); currentWrite.setChars(chars);
appendable.append(currentWrite, offset, offset + length); appendable.append(currentWrite, offset, offset + length);
} }
@Override public void flush() {} @Override
@Override public void close() {} public void flush() {}
@Override
public void close() {}
// Override these methods for better performance // Override these methods for better performance
// They would otherwise unnecessarily create Strings or char arrays // They would otherwise unnecessarily create Strings or char arrays
@Override public void write(int i) throws IOException { @Override
public void write(int i) throws IOException {
appendable.append((char) i); appendable.append((char) i);
} }
@Override public void write(String str, int off, int len) throws IOException { @Override
public void write(String str, int off, int len) throws IOException {
// Appendable.append turns null -> "null", which is not desired here // Appendable.append turns null -> "null", which is not desired here
Objects.requireNonNull(str); Objects.requireNonNull(str);
appendable.append(str, off, off + len); appendable.append(str, off, off + len);
} }
@Override public Writer append(CharSequence csq) throws IOException { @Override
public Writer append(CharSequence csq) throws IOException {
appendable.append(csq); appendable.append(csq);
return this; return this;
} }
@Override public Writer append(CharSequence csq, int start, int end) throws IOException { @Override
public Writer append(CharSequence csq, int start, int end) throws IOException {
appendable.append(csq, start, end); appendable.append(csq, start, end);
return this; return this;
} }
/** /** A mutable char sequence pointing at a single char[]. */
* A mutable char sequence pointing at a single char[].
*/
private static class CurrentWrite implements CharSequence { private static class CurrentWrite implements CharSequence {
private char[] chars; private char[] chars;
private String cachedString; private String cachedString;
@ -133,18 +130,24 @@ public final class Streams {
this.cachedString = null; this.cachedString = null;
} }
@Override public int length() { @Override
public int length() {
return chars.length; return chars.length;
} }
@Override public char charAt(int i) {
@Override
public char charAt(int i) {
return chars[i]; return chars[i];
} }
@Override public CharSequence subSequence(int start, int end) {
@Override
public CharSequence subSequence(int start, int end) {
return new String(chars, start, end - start); return new String(chars, start, end - start);
} }
// Must return string representation to satisfy toString() contract // Must return string representation to satisfy toString() contract
@Override public String toString() { @Override
public String toString() {
if (cachedString == null) { if (cachedString == null) {
cachedString = new String(chars); cachedString = new String(chars);
} }

View File

@ -3,9 +3,7 @@ package com.google.gson.internal;
public class TroubleshootingGuide { public class TroubleshootingGuide {
private TroubleshootingGuide() {} private TroubleshootingGuide() {}
/** /** Creates a URL referring to the specified troubleshooting section. */
* Creates a URL referring to the specified troubleshooting section.
*/
public static String createUrl(String id) { public static String createUrl(String id) {
return "https://github.com/google/gson/blob/main/Troubleshooting.md#" + id; return "https://github.com/google/gson/blob/main/Troubleshooting.md#" + id;
} }

View File

@ -31,14 +31,15 @@ public abstract class UnsafeAllocator {
public abstract <T> T newInstance(Class<T> c) throws Exception; public abstract <T> T newInstance(Class<T> c) throws Exception;
/** /**
* Asserts that the class is instantiable. This check should have already occurred * Asserts that the class is instantiable. This check should have already occurred in {@link
* in {@link ConstructorConstructor}; this check here acts as safeguard since trying * ConstructorConstructor}; this check here acts as safeguard since trying to use Unsafe for
* to use Unsafe for non-instantiable classes might crash the JVM on some devices. * non-instantiable classes might crash the JVM on some devices.
*/ */
private static void assertInstantiable(Class<?> c) { private static void assertInstantiable(Class<?> c) {
String exceptionMessage = ConstructorConstructor.checkInstantiable(c); String exceptionMessage = ConstructorConstructor.checkInstantiable(c);
if (exceptionMessage != null) { if (exceptionMessage != null) {
throw new AssertionError("UnsafeAllocator is used for non-instantiable type: " + exceptionMessage); throw new AssertionError(
"UnsafeAllocator is used for non-instantiable type: " + exceptionMessage);
} }
} }
@ -73,12 +74,12 @@ public abstract class UnsafeAllocator {
// private static native Object newInstance(Class<?> instantiationClass, int methodId); // private static native Object newInstance(Class<?> instantiationClass, int methodId);
// } // }
try { try {
Method getConstructorId = ObjectStreamClass.class Method getConstructorId =
.getDeclaredMethod("getConstructorId", Class.class); ObjectStreamClass.class.getDeclaredMethod("getConstructorId", Class.class);
getConstructorId.setAccessible(true); getConstructorId.setAccessible(true);
final int constructorId = (Integer) getConstructorId.invoke(null, Object.class); final int constructorId = (Integer) getConstructorId.invoke(null, Object.class);
final Method newInstance = ObjectStreamClass.class final Method newInstance =
.getDeclaredMethod("newInstance", Class.class, int.class); ObjectStreamClass.class.getDeclaredMethod("newInstance", Class.class, int.class);
newInstance.setAccessible(true); newInstance.setAccessible(true);
return new UnsafeAllocator() { return new UnsafeAllocator() {
@Override @Override
@ -98,8 +99,8 @@ public abstract class UnsafeAllocator {
// Class<?> instantiationClass, Class<?> constructorClass); // Class<?> instantiationClass, Class<?> constructorClass);
// } // }
try { try {
final Method newInstance = ObjectInputStream.class final Method newInstance =
.getDeclaredMethod("newInstance", Class.class, Class.class); ObjectInputStream.class.getDeclaredMethod("newInstance", Class.class, Class.class);
newInstance.setAccessible(true); newInstance.setAccessible(true);
return new UnsafeAllocator() { return new UnsafeAllocator() {
@Override @Override
@ -117,7 +118,10 @@ public abstract class UnsafeAllocator {
return new UnsafeAllocator() { return new UnsafeAllocator() {
@Override @Override
public <T> T newInstance(Class<T> c) { public <T> T newInstance(Class<T> c) {
throw new UnsupportedOperationException("Cannot allocate " + c + ". Usage of JDK sun.misc.Unsafe is enabled, " throw new UnsupportedOperationException(
"Cannot allocate "
+ c
+ ". Usage of JDK sun.misc.Unsafe is enabled, "
+ "but it could not be used. Make sure your runtime is configured correctly."); + "but it could not be used. Make sure your runtime is configured correctly.");
} }
}; };

View File

@ -30,14 +30,15 @@ import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
/** /** Adapt an array of objects. */
* Adapt an array of objects.
*/
public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> { public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { public static final TypeAdapterFactory FACTORY =
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType(); Type type = typeToken.getType();
if (!(type instanceof GenericArrayType || (type instanceof Class && ((Class<?>) type).isArray()))) { if (!(type instanceof GenericArrayType
|| (type instanceof Class && ((Class<?>) type).isArray()))) {
return null; return null;
} }
@ -45,7 +46,8 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType)); TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapter<T> arrayAdapter = new ArrayTypeAdapter( TypeAdapter<T> arrayAdapter =
new ArrayTypeAdapter(
gson, componentTypeAdapter, $Gson$Types.getRawType(componentType)); gson, componentTypeAdapter, $Gson$Types.getRawType(componentType));
return arrayAdapter; return arrayAdapter;
} }
@ -54,13 +56,15 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
private final Class<E> componentType; private final Class<E> componentType;
private final TypeAdapter<E> componentTypeAdapter; private final TypeAdapter<E> componentTypeAdapter;
public ArrayTypeAdapter(Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) { public ArrayTypeAdapter(
Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) {
this.componentTypeAdapter = this.componentTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<>(context, componentTypeAdapter, componentType); new TypeAdapterRuntimeTypeWrapper<>(context, componentTypeAdapter, componentType);
this.componentType = componentType; this.componentType = componentType;
} }
@Override public Object read(JsonReader in) throws IOException { @Override
public Object read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -91,7 +95,8 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
} }
} }
@Override public void write(JsonWriter out, Object array) throws IOException { @Override
public void write(JsonWriter out, Object array) throws IOException {
if (array == null) { if (array == null) {
out.nullValue(); out.nullValue();
return; return;

View File

@ -30,9 +30,7 @@ import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Collection; import java.util.Collection;
/** /** Adapt a homogeneous collection of objects. */
* Adapt a homogeneous collection of objects.
*/
public final class CollectionTypeAdapterFactory implements TypeAdapterFactory { public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor; private final ConstructorConstructor constructorConstructor;
@ -62,7 +60,9 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
private final TypeAdapter<E> elementTypeAdapter; private final TypeAdapter<E> elementTypeAdapter;
private final ObjectConstructor<? extends Collection<E>> constructor; private final ObjectConstructor<? extends Collection<E>> constructor;
public Adapter(Gson context, Type elementType, public Adapter(
Gson context,
Type elementType,
TypeAdapter<E> elementTypeAdapter, TypeAdapter<E> elementTypeAdapter,
ObjectConstructor<? extends Collection<E>> constructor) { ObjectConstructor<? extends Collection<E>> constructor) {
this.elementTypeAdapter = this.elementTypeAdapter =
@ -70,7 +70,8 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
this.constructor = constructor; this.constructor = constructor;
} }
@Override public Collection<E> read(JsonReader in) throws IOException { @Override
public Collection<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -86,7 +87,8 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
return collection; return collection;
} }
@Override public void write(JsonWriter out, Collection<E> collection) throws IOException { @Override
public void write(JsonWriter out, Collection<E> collection) throws IOException {
if (collection == null) { if (collection == null) {
out.nullValue(); out.nullValue();
return; return;

View File

@ -27,7 +27,6 @@ import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
@ -38,36 +37,42 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
/** /**
* Adapter for Date. Although this class appears stateless, it is not. * Adapter for Date. Although this class appears stateless, it is not. DateFormat captures its time
* DateFormat captures its time zone and locale when it is created, which gives * zone and locale when it is created, which gives this class state. DateFormat isn't thread safe
* this class state. DateFormat isn't thread safe either, so this class has * either, so this class has to synchronize its read and write methods.
* to synchronize its read and write methods.
*/ */
public final class DateTypeAdapter extends TypeAdapter<Date> { public final class DateTypeAdapter extends TypeAdapter<Date> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { public static final TypeAdapterFactory FACTORY =
new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
return typeToken.getRawType() == Date.class ? (TypeAdapter<T>) new DateTypeAdapter() : null; public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
return typeToken.getRawType() == Date.class
? (TypeAdapter<T>) new DateTypeAdapter()
: null;
} }
}; };
/** /**
* List of 1 or more different date formats used for de-serialization attempts. * List of 1 or more different date formats used for de-serialization attempts. The first of them
* The first of them (default US format) is used for serialization as well. * (default US format) is used for serialization as well.
*/ */
private final List<DateFormat> dateFormats = new ArrayList<>(); private final List<DateFormat> dateFormats = new ArrayList<>();
public DateTypeAdapter() { public DateTypeAdapter() {
dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US)); dateFormats.add(
DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) { if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)); dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
} }
if (JavaVersion.isJava9OrLater()) { if (JavaVersion.isJava9OrLater()) {
dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT)); dateFormats.add(
PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT));
} }
} }
@Override public Date read(JsonReader in) throws IOException { @Override
public Date read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -89,11 +94,13 @@ public final class DateTypeAdapter extends TypeAdapter<Date> {
try { try {
return ISO8601Utils.parse(s, new ParsePosition(0)); return ISO8601Utils.parse(s, new ParsePosition(0));
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e); throw new JsonSyntaxException(
"Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e);
} }
} }
@Override public void write(JsonWriter out, Date value) throws IOException { @Override
public void write(JsonWriter out, Date value) throws IOException {
if (value == null) { if (value == null) {
out.nullValue(); out.nullValue();
return; return;

View File

@ -37,9 +37,8 @@ import java.util.Locale;
import java.util.Objects; import java.util.Objects;
/** /**
* This type adapter supports subclasses of date by defining a * This type adapter supports subclasses of date by defining a {@link
* {@link DefaultDateTypeAdapter.DateType} and then using its {@code createAdapterFactory} * DefaultDateTypeAdapter.DateType} and then using its {@code createAdapterFactory} methods.
* methods.
* *
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
@ -47,9 +46,11 @@ import java.util.Objects;
public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T> { public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T> {
private static final String SIMPLE_NAME = "DefaultDateTypeAdapter"; private static final String SIMPLE_NAME = "DefaultDateTypeAdapter";
public static abstract class DateType<T extends Date> { public abstract static class DateType<T extends Date> {
public static final DateType<Date> DATE = new DateType<Date>(Date.class) { public static final DateType<Date> DATE =
@Override protected Date deserialize(Date date) { new DateType<Date>(Date.class) {
@Override
protected Date deserialize(Date date) {
return date; return date;
} }
}; };
@ -79,15 +80,16 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
} }
public final TypeAdapterFactory createDefaultsAdapterFactory() { public final TypeAdapterFactory createDefaultsAdapterFactory() {
return createFactory(new DefaultDateTypeAdapter<>(this, DateFormat.DEFAULT, DateFormat.DEFAULT)); return createFactory(
new DefaultDateTypeAdapter<>(this, DateFormat.DEFAULT, DateFormat.DEFAULT));
} }
} }
private final DateType<T> dateType; private final DateType<T> dateType;
/** /**
* List of 1 or more different date formats used for de-serialization attempts. * List of 1 or more different date formats used for de-serialization attempts. The first of them
* The first of them is used for serialization as well. * is used for serialization as well.
*/ */
private final List<DateFormat> dateFormats = new ArrayList<>(); private final List<DateFormat> dateFormats = new ArrayList<>();
@ -163,7 +165,8 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
try { try {
return ISO8601Utils.parse(s, new ParsePosition(0)); return ISO8601Utils.parse(s, new ParsePosition(0));
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e); throw new JsonSyntaxException(
"Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e);
} }
} }

View File

@ -36,29 +36,26 @@ import java.util.concurrent.ConcurrentMap;
*/ */
public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory { public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {
private static class DummyTypeAdapterFactory implements TypeAdapterFactory { private static class DummyTypeAdapterFactory implements TypeAdapterFactory {
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
throw new AssertionError("Factory should not be used"); throw new AssertionError("Factory should not be used");
} }
} }
/** /** Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter} on a class. */
* Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter} private static final TypeAdapterFactory TREE_TYPE_CLASS_DUMMY_FACTORY =
* on a class. new DummyTypeAdapterFactory();
*/
private static final TypeAdapterFactory TREE_TYPE_CLASS_DUMMY_FACTORY = new DummyTypeAdapterFactory();
/** /** Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter} on a field. */
* Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter} private static final TypeAdapterFactory TREE_TYPE_FIELD_DUMMY_FACTORY =
* on a field. new DummyTypeAdapterFactory();
*/
private static final TypeAdapterFactory TREE_TYPE_FIELD_DUMMY_FACTORY = new DummyTypeAdapterFactory();
private final ConstructorConstructor constructorConstructor; private final ConstructorConstructor constructorConstructor;
/** /**
* For a class, if it is annotated with {@code @JsonAdapter} and refers to a {@link TypeAdapterFactory}, * For a class, if it is annotated with {@code @JsonAdapter} and refers to a {@link
* stores the factory instance in case it has been requested already. * TypeAdapterFactory}, stores the factory instance in case it has been requested already. Has to
* Has to be a {@link ConcurrentMap} because {@link Gson} guarantees to be thread-safe. * be a {@link ConcurrentMap} because {@link Gson} guarantees to be thread-safe.
*/ */
// Note: In case these strong reference to TypeAdapterFactory instances are considered // Note: In case these strong reference to TypeAdapterFactory instances are considered
// a memory leak in the future, could consider switching to WeakReference<TypeAdapterFactory> // a memory leak in the future, could consider switching to WeakReference<TypeAdapterFactory>
@ -74,7 +71,8 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
return rawType.getAnnotation(JsonAdapter.class); return rawType.getAnnotation(JsonAdapter.class);
} }
@SuppressWarnings("unchecked") // this is not safe; requires that user has specified correct adapter class for @JsonAdapter // this is not safe; requires that user has specified correct adapter class for @JsonAdapter
@SuppressWarnings("unchecked")
@Override @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) { public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
Class<? super T> rawType = targetType.getRawType(); Class<? super T> rawType = targetType.getRawType();
@ -82,13 +80,16 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
if (annotation == null) { if (annotation == null) {
return null; return null;
} }
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation, true); return (TypeAdapter<T>)
getTypeAdapter(constructorConstructor, gson, targetType, annotation, true);
} }
// Separate helper method to make sure callers create adapter in a consistent way // Separate helper method to make sure callers create adapter in a consistent way
private static Object createAdapter(ConstructorConstructor constructorConstructor, Class<?> adapterClass) { private static Object createAdapter(
// TODO: The exception messages created by ConstructorConstructor are currently written in the context of ConstructorConstructor constructorConstructor, Class<?> adapterClass) {
// deserialization and for example suggest usage of TypeAdapter, which would not work for @JsonAdapter usage // TODO: The exception messages created by ConstructorConstructor are currently written in the
// context of deserialization and for example suggest usage of TypeAdapter, which would not work
// for @JsonAdapter usage
return constructorConstructor.get(TypeToken.get(adapterClass)).construct(); return constructorConstructor.get(TypeToken.get(adapterClass)).construct();
} }
@ -98,8 +99,12 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
return existingFactory != null ? existingFactory : factory; return existingFactory != null ? existingFactory : factory;
} }
TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson, TypeAdapter<?> getTypeAdapter(
TypeToken<?> type, JsonAdapter annotation, boolean isClassAnnotation) { ConstructorConstructor constructorConstructor,
Gson gson,
TypeToken<?> type,
JsonAdapter annotation,
boolean isClassAnnotation) {
Object instance = createAdapter(constructorConstructor, annotation.value()); Object instance = createAdapter(constructorConstructor, annotation.value());
TypeAdapter<?> typeAdapter; TypeAdapter<?> typeAdapter;
@ -115,30 +120,33 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
typeAdapter = factory.create(gson, type); typeAdapter = factory.create(gson, type);
} else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) { } else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) {
JsonSerializer<?> serializer = instance instanceof JsonSerializer JsonSerializer<?> serializer =
? (JsonSerializer<?>) instance instance instanceof JsonSerializer ? (JsonSerializer<?>) instance : null;
: null; JsonDeserializer<?> deserializer =
JsonDeserializer<?> deserializer = instance instanceof JsonDeserializer instance instanceof JsonDeserializer ? (JsonDeserializer<?>) instance : null;
? (JsonDeserializer<?>) instance
: null;
// Uses dummy factory instances because TreeTypeAdapter needs a 'skipPast' factory for `Gson.getDelegateAdapter` // Uses dummy factory instances because TreeTypeAdapter needs a 'skipPast' factory for
// call and has to differentiate there whether TreeTypeAdapter was created for @JsonAdapter on class or field // `Gson.getDelegateAdapter` call and has to differentiate there whether TreeTypeAdapter was
// created for @JsonAdapter on class or field
TypeAdapterFactory skipPast; TypeAdapterFactory skipPast;
if (isClassAnnotation) { if (isClassAnnotation) {
skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY; skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY;
} else { } else {
skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY; skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY;
} }
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapter<?> tempAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe); TypeAdapter<?> tempAdapter =
new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe);
typeAdapter = tempAdapter; typeAdapter = tempAdapter;
// TreeTypeAdapter handles nullSafe; don't additionally call `nullSafe()` // TreeTypeAdapter handles nullSafe; don't additionally call `nullSafe()`
nullSafe = false; nullSafe = false;
} else { } else {
throw new IllegalArgumentException("Invalid attempt to bind an instance of " throw new IllegalArgumentException(
+ instance.getClass().getName() + " as a @JsonAdapter for " + type.toString() "Invalid attempt to bind an instance of "
+ instance.getClass().getName()
+ " as a @JsonAdapter for "
+ type.toString()
+ ". @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory," + ". @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory,"
+ " JsonSerializer or JsonDeserializer."); + " JsonSerializer or JsonDeserializer.");
} }
@ -173,8 +181,8 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
// If no factory has been created for the type yet check manually for a @JsonAdapter annotation // If no factory has been created for the type yet check manually for a @JsonAdapter annotation
// which specifies a TypeAdapterFactory // which specifies a TypeAdapterFactory
// Otherwise behavior would not be consistent, depending on whether or not adapter had been requested // Otherwise behavior would not be consistent, depending on whether or not adapter had been
// before call to `isClassJsonAdapterFactory` was made // requested before call to `isClassJsonAdapterFactory` was made
JsonAdapter annotation = getAnnotation(rawType); JsonAdapter annotation = getAnnotation(rawType);
if (annotation == null) { if (annotation == null) {
return false; return false;

View File

@ -32,17 +32,20 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
/** /**
* This reader walks the elements of a JsonElement as if it was coming from a * This reader walks the elements of a JsonElement as if it was coming from a character stream.
* character stream.
* *
* @author Jesse Wilson * @author Jesse Wilson
*/ */
public final class JsonTreeReader extends JsonReader { public final class JsonTreeReader extends JsonReader {
private static final Reader UNREADABLE_READER = new Reader() { private static final Reader UNREADABLE_READER =
@Override public int read(char[] buffer, int offset, int count) { new Reader() {
@Override
public int read(char[] buffer, int offset, int count) {
throw new AssertionError(); throw new AssertionError();
} }
@Override public void close() {
@Override
public void close() {
throw new AssertionError(); throw new AssertionError();
} }
}; };
@ -70,14 +73,16 @@ public final class JsonTreeReader extends JsonReader {
push(element); push(element);
} }
@Override public void beginArray() throws IOException { @Override
public void beginArray() throws IOException {
expect(JsonToken.BEGIN_ARRAY); expect(JsonToken.BEGIN_ARRAY);
JsonArray array = (JsonArray) peekStack(); JsonArray array = (JsonArray) peekStack();
push(array.iterator()); push(array.iterator());
pathIndices[stackSize - 1] = 0; pathIndices[stackSize - 1] = 0;
} }
@Override public void endArray() throws IOException { @Override
public void endArray() throws IOException {
expect(JsonToken.END_ARRAY); expect(JsonToken.END_ARRAY);
popStack(); // empty iterator popStack(); // empty iterator
popStack(); // array popStack(); // array
@ -86,13 +91,15 @@ public final class JsonTreeReader extends JsonReader {
} }
} }
@Override public void beginObject() throws IOException { @Override
public void beginObject() throws IOException {
expect(JsonToken.BEGIN_OBJECT); expect(JsonToken.BEGIN_OBJECT);
JsonObject object = (JsonObject) peekStack(); JsonObject object = (JsonObject) peekStack();
push(object.entrySet().iterator()); push(object.entrySet().iterator());
} }
@Override public void endObject() throws IOException { @Override
public void endObject() throws IOException {
expect(JsonToken.END_OBJECT); expect(JsonToken.END_OBJECT);
pathNames[stackSize - 1] = null; // Free the last path name so that it can be garbage collected pathNames[stackSize - 1] = null; // Free the last path name so that it can be garbage collected
popStack(); // empty iterator popStack(); // empty iterator
@ -102,12 +109,16 @@ public final class JsonTreeReader extends JsonReader {
} }
} }
@Override public boolean hasNext() throws IOException { @Override
public boolean hasNext() throws IOException {
JsonToken token = peek(); JsonToken token = peek();
return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY && token != JsonToken.END_DOCUMENT; return token != JsonToken.END_OBJECT
&& token != JsonToken.END_ARRAY
&& token != JsonToken.END_DOCUMENT;
} }
@Override public JsonToken peek() throws IOException { @Override
public JsonToken peek() throws IOException {
if (stackSize == 0) { if (stackSize == 0) {
return JsonToken.END_DOCUMENT; return JsonToken.END_DOCUMENT;
} }
@ -146,7 +157,8 @@ public final class JsonTreeReader extends JsonReader {
} else if (o == SENTINEL_CLOSED) { } else if (o == SENTINEL_CLOSED) {
throw new IllegalStateException("JsonReader is closed"); throw new IllegalStateException("JsonReader is closed");
} else { } else {
throw new MalformedJsonException("Custom JsonElement subclass " + o.getClass().getName() + " is not supported"); throw new MalformedJsonException(
"Custom JsonElement subclass " + o.getClass().getName() + " is not supported");
} }
} }
@ -178,11 +190,13 @@ public final class JsonTreeReader extends JsonReader {
return result; return result;
} }
@Override public String nextName() throws IOException { @Override
public String nextName() throws IOException {
return nextName(false); return nextName(false);
} }
@Override public String nextString() throws IOException { @Override
public String nextString() throws IOException {
JsonToken token = peek(); JsonToken token = peek();
if (token != JsonToken.STRING && token != JsonToken.NUMBER) { if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
throw new IllegalStateException( throw new IllegalStateException(
@ -195,7 +209,8 @@ public final class JsonTreeReader extends JsonReader {
return result; return result;
} }
@Override public boolean nextBoolean() throws IOException { @Override
public boolean nextBoolean() throws IOException {
expect(JsonToken.BOOLEAN); expect(JsonToken.BOOLEAN);
boolean result = ((JsonPrimitive) popStack()).getAsBoolean(); boolean result = ((JsonPrimitive) popStack()).getAsBoolean();
if (stackSize > 0) { if (stackSize > 0) {
@ -204,7 +219,8 @@ public final class JsonTreeReader extends JsonReader {
return result; return result;
} }
@Override public void nextNull() throws IOException { @Override
public void nextNull() throws IOException {
expect(JsonToken.NULL); expect(JsonToken.NULL);
popStack(); popStack();
if (stackSize > 0) { if (stackSize > 0) {
@ -212,7 +228,8 @@ public final class JsonTreeReader extends JsonReader {
} }
} }
@Override public double nextDouble() throws IOException { @Override
public double nextDouble() throws IOException {
JsonToken token = peek(); JsonToken token = peek();
if (token != JsonToken.NUMBER && token != JsonToken.STRING) { if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
throw new IllegalStateException( throw new IllegalStateException(
@ -229,7 +246,8 @@ public final class JsonTreeReader extends JsonReader {
return result; return result;
} }
@Override public long nextLong() throws IOException { @Override
public long nextLong() throws IOException {
JsonToken token = peek(); JsonToken token = peek();
if (token != JsonToken.NUMBER && token != JsonToken.STRING) { if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
throw new IllegalStateException( throw new IllegalStateException(
@ -243,7 +261,8 @@ public final class JsonTreeReader extends JsonReader {
return result; return result;
} }
@Override public int nextInt() throws IOException { @Override
public int nextInt() throws IOException {
JsonToken token = peek(); JsonToken token = peek();
if (token != JsonToken.NUMBER && token != JsonToken.STRING) { if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
throw new IllegalStateException( throw new IllegalStateException(
@ -270,12 +289,14 @@ public final class JsonTreeReader extends JsonReader {
return element; return element;
} }
@Override public void close() throws IOException { @Override
stack = new Object[] { SENTINEL_CLOSED }; public void close() throws IOException {
stack = new Object[] {SENTINEL_CLOSED};
stackSize = 1; stackSize = 1;
} }
@Override public void skipValue() throws IOException { @Override
public void skipValue() throws IOException {
JsonToken peeked = peek(); JsonToken peeked = peek();
switch (peeked) { switch (peeked) {
case NAME: case NAME:
@ -300,7 +321,8 @@ public final class JsonTreeReader extends JsonReader {
} }
} }
@Override public String toString() { @Override
public String toString() {
return getClass().getSimpleName() + locationString(); return getClass().getSimpleName() + locationString();
} }
@ -348,11 +370,13 @@ public final class JsonTreeReader extends JsonReader {
return result.toString(); return result.toString();
} }
@Override public String getPreviousPath() { @Override
public String getPreviousPath() {
return getPath(true); return getPath(true);
} }
@Override public String getPath() { @Override
public String getPath() {
return getPath(false); return getPath(false);
} }

View File

@ -29,21 +29,26 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /** This writer creates a JsonElement. */
* This writer creates a JsonElement.
*/
public final class JsonTreeWriter extends JsonWriter { public final class JsonTreeWriter extends JsonWriter {
private static final Writer UNWRITABLE_WRITER = new Writer() { private static final Writer UNWRITABLE_WRITER =
@Override public void write(char[] buffer, int offset, int counter) { new Writer() {
@Override
public void write(char[] buffer, int offset, int counter) {
throw new AssertionError(); throw new AssertionError();
} }
@Override public void flush() {
@Override
public void flush() {
throw new AssertionError(); throw new AssertionError();
} }
@Override public void close() {
@Override
public void close() {
throw new AssertionError(); throw new AssertionError();
} }
}; };
/** Added to the top of the stack when this writer is closed to cause following ops to fail. */ /** Added to the top of the stack when this writer is closed to cause following ops to fail. */
private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed"); private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed");
@ -60,9 +65,7 @@ public final class JsonTreeWriter extends JsonWriter {
super(UNWRITABLE_WRITER); super(UNWRITABLE_WRITER);
} }
/** /** Returns the top level object produced by this writer. */
* Returns the top level object produced by this writer.
*/
public JsonElement get() { public JsonElement get() {
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
throw new IllegalStateException("Expected one JSON element but was " + stack); throw new IllegalStateException("Expected one JSON element but was " + stack);
@ -94,7 +97,8 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter beginArray() throws IOException { @Override
public JsonWriter beginArray() throws IOException {
JsonArray array = new JsonArray(); JsonArray array = new JsonArray();
put(array); put(array);
stack.add(array); stack.add(array);
@ -102,7 +106,8 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter endArray() throws IOException { @Override
public JsonWriter endArray() throws IOException {
if (stack.isEmpty() || pendingName != null) { if (stack.isEmpty() || pendingName != null) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
@ -115,7 +120,8 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter beginObject() throws IOException { @Override
public JsonWriter beginObject() throws IOException {
JsonObject object = new JsonObject(); JsonObject object = new JsonObject();
put(object); put(object);
stack.add(object); stack.add(object);
@ -123,7 +129,8 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter endObject() throws IOException { @Override
public JsonWriter endObject() throws IOException {
if (stack.isEmpty() || pendingName != null) { if (stack.isEmpty() || pendingName != null) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
@ -136,7 +143,8 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter name(String name) throws IOException { @Override
public JsonWriter name(String name) throws IOException {
Objects.requireNonNull(name, "name == null"); Objects.requireNonNull(name, "name == null");
if (stack.isEmpty() || pendingName != null) { if (stack.isEmpty() || pendingName != null) {
throw new IllegalStateException("Did not expect a name"); throw new IllegalStateException("Did not expect a name");
@ -150,7 +158,8 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter value(String value) throws IOException { @Override
public JsonWriter value(String value) throws IOException {
if (value == null) { if (value == null) {
return nullValue(); return nullValue();
} }
@ -158,24 +167,28 @@ public final class JsonTreeWriter extends JsonWriter {
return this; return this;
} }
@Override public JsonWriter jsonValue(String value) throws IOException { @Override
public JsonWriter jsonValue(String value) throws IOException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter nullValue() throws IOException { @Override
public JsonWriter nullValue() throws IOException {
put(JsonNull.INSTANCE); put(JsonNull.INSTANCE);
return this; return this;
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter value(boolean value) throws IOException { @Override
public JsonWriter value(boolean value) throws IOException {
put(new JsonPrimitive(value)); put(new JsonPrimitive(value));
return this; return this;
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter value(Boolean value) throws IOException { @Override
public JsonWriter value(Boolean value) throws IOException {
if (value == null) { if (value == null) {
return nullValue(); return nullValue();
} }
@ -184,7 +197,8 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter value(float value) throws IOException { @Override
public JsonWriter value(float value) throws IOException {
if (!isLenient() && (Float.isNaN(value) || Float.isInfinite(value))) { if (!isLenient() && (Float.isNaN(value) || Float.isInfinite(value))) {
throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value); throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
} }
@ -193,7 +207,8 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter value(double value) throws IOException { @Override
public JsonWriter value(double value) throws IOException {
if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) { if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) {
throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value); throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
} }
@ -202,13 +217,15 @@ public final class JsonTreeWriter extends JsonWriter {
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter value(long value) throws IOException { @Override
public JsonWriter value(long value) throws IOException {
put(new JsonPrimitive(value)); put(new JsonPrimitive(value));
return this; return this;
} }
@CanIgnoreReturnValue @CanIgnoreReturnValue
@Override public JsonWriter value(Number value) throws IOException { @Override
public JsonWriter value(Number value) throws IOException {
if (value == null) { if (value == null) {
return nullValue(); return nullValue();
} }
@ -224,10 +241,11 @@ public final class JsonTreeWriter extends JsonWriter {
return this; return this;
} }
@Override public void flush() throws IOException { @Override
} public void flush() throws IOException {}
@Override public void close() throws IOException { @Override
public void close() throws IOException {
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
throw new IOException("Incomplete document"); throw new IOException("Incomplete document");
} }

View File

@ -41,47 +41,57 @@ import java.util.Map;
* Adapts maps to either JSON objects or JSON arrays. * Adapts maps to either JSON objects or JSON arrays.
* *
* <h2>Maps as JSON objects</h2> * <h2>Maps as JSON objects</h2>
* For primitive keys or when complex map key serialization is not enabled, this *
* converts Java {@link Map Maps} to JSON Objects. This requires that map keys * For primitive keys or when complex map key serialization is not enabled, this converts Java
* can be serialized as strings; this is insufficient for some key types. For * {@link Map Maps} to JSON Objects. This requires that map keys can be serialized as strings; this
* example, consider a map whose keys are points on a grid. The default JSON * is insufficient for some key types. For example, consider a map whose keys are points on a grid.
* form encodes reasonably: <pre> {@code * The default JSON form encodes reasonably:
*
* <pre>{@code
* Map<Point, String> original = new LinkedHashMap<>(); * Map<Point, String> original = new LinkedHashMap<>();
* original.put(new Point(5, 6), "a"); * original.put(new Point(5, 6), "a");
* original.put(new Point(8, 8), "b"); * original.put(new Point(8, 8), "b");
* System.out.println(gson.toJson(original, type)); * System.out.println(gson.toJson(original, type));
* }</pre> * }</pre>
* The above code prints this JSON object:<pre> {@code *
* The above code prints this JSON object:
*
* <pre>{@code
* { * {
* "(5,6)": "a", * "(5,6)": "a",
* "(8,8)": "b" * "(8,8)": "b"
* } * }
* }</pre> * }</pre>
* But GSON is unable to deserialize this value because the JSON string name is *
* just the {@link Object#toString() toString()} of the map key. Attempting to * But GSON is unable to deserialize this value because the JSON string name is just the {@link
* convert the above JSON to an object fails with a parse exception: * Object#toString() toString()} of the map key. Attempting to convert the above JSON to an object
* fails with a parse exception:
*
* <pre>com.google.gson.JsonParseException: Expecting object found: "(5,6)" * <pre>com.google.gson.JsonParseException: Expecting object found: "(5,6)"
* at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler * at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler
* at com.google.gson.ObjectNavigator.navigateClassFields * at com.google.gson.ObjectNavigator.navigateClassFields
* ...</pre> * ...</pre>
* *
* <h2>Maps as JSON arrays</h2> * <h2>Maps as JSON arrays</h2>
* An alternative approach taken by this type adapter when it is required and *
* complex map key serialization is enabled is to encode maps as arrays of map * An alternative approach taken by this type adapter when it is required and complex map key
* entries. Each map entry is a two element array containing a key and a value. * serialization is enabled is to encode maps as arrays of map entries. Each map entry is a two
* This approach is more flexible because any type can be used as the map's key; * element array containing a key and a value. This approach is more flexible because any type can
* not just strings. But it's also less portable because the receiver of such * be used as the map's key; not just strings. But it's also less portable because the receiver of
* JSON must be aware of the map entry convention. * such JSON must be aware of the map entry convention.
* *
* <p>Register this adapter when you are creating your GSON instance. * <p>Register this adapter when you are creating your GSON instance.
* <pre> {@code *
* <pre>{@code
* Gson gson = new GsonBuilder() * Gson gson = new GsonBuilder()
* .registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter()) * .registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter())
* .create(); * .create();
* }</pre> * }</pre>
* This will change the structure of the JSON emitted by the code above. Now we *
* get an array. In this case the arrays elements are map entries: * This will change the structure of the JSON emitted by the code above. Now we get an array. In
* <pre> {@code * this case the arrays elements are map entries:
*
* <pre>{@code
* [ * [
* [ * [
* { * {
@ -99,20 +109,21 @@ import java.util.Map;
* ] * ]
* ] * ]
* }</pre> * }</pre>
* This format will serialize and deserialize just fine as long as this adapter *
* is registered. * This format will serialize and deserialize just fine as long as this adapter is registered.
*/ */
public final class MapTypeAdapterFactory implements TypeAdapterFactory { public final class MapTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor; private final ConstructorConstructor constructorConstructor;
final boolean complexMapKeySerialization; final boolean complexMapKeySerialization;
public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor, public MapTypeAdapterFactory(
boolean complexMapKeySerialization) { ConstructorConstructor constructorConstructor, boolean complexMapKeySerialization) {
this.constructorConstructor = constructorConstructor; this.constructorConstructor = constructorConstructor;
this.complexMapKeySerialization = complexMapKeySerialization; this.complexMapKeySerialization = complexMapKeySerialization;
} }
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType(); Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType(); Class<? super T> rawType = typeToken.getRawType();
@ -127,14 +138,13 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
// we don't define a type parameter for the key or value types // we don't define a type parameter for the key or value types
TypeAdapter<T> result = new Adapter(gson, keyAndValueTypes[0], keyAdapter, TypeAdapter<T> result =
keyAndValueTypes[1], valueAdapter, constructor); new Adapter(
gson, keyAndValueTypes[0], keyAdapter, keyAndValueTypes[1], valueAdapter, constructor);
return result; return result;
} }
/** /** Returns a type adapter that writes the value as a string. */
* Returns a type adapter that writes the value as a string.
*/
private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) { private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) {
return (keyType == boolean.class || keyType == Boolean.class) return (keyType == boolean.class || keyType == Boolean.class)
? TypeAdapters.BOOLEAN_AS_STRING ? TypeAdapters.BOOLEAN_AS_STRING
@ -146,17 +156,21 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
private final TypeAdapter<V> valueTypeAdapter; private final TypeAdapter<V> valueTypeAdapter;
private final ObjectConstructor<? extends Map<K, V>> constructor; private final ObjectConstructor<? extends Map<K, V>> constructor;
public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter, public Adapter(
Type valueType, TypeAdapter<V> valueTypeAdapter, Gson context,
Type keyType,
TypeAdapter<K> keyTypeAdapter,
Type valueType,
TypeAdapter<V> valueTypeAdapter,
ObjectConstructor<? extends Map<K, V>> constructor) { ObjectConstructor<? extends Map<K, V>> constructor) {
this.keyTypeAdapter = this.keyTypeAdapter = new TypeAdapterRuntimeTypeWrapper<>(context, keyTypeAdapter, keyType);
new TypeAdapterRuntimeTypeWrapper<>(context, keyTypeAdapter, keyType);
this.valueTypeAdapter = this.valueTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<>(context, valueTypeAdapter, valueType); new TypeAdapterRuntimeTypeWrapper<>(context, valueTypeAdapter, valueType);
this.constructor = constructor; this.constructor = constructor;
} }
@Override public Map<K, V> read(JsonReader in) throws IOException { @Override
public Map<K, V> read(JsonReader in) throws IOException {
JsonToken peek = in.peek(); JsonToken peek = in.peek();
if (peek == JsonToken.NULL) { if (peek == JsonToken.NULL) {
in.nextNull(); in.nextNull();
@ -194,7 +208,8 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
return map; return map;
} }
@Override public void write(JsonWriter out, Map<K, V> map) throws IOException { @Override
public void write(JsonWriter out, Map<K, V> map) throws IOException {
if (map == null) { if (map == null) {
out.nullValue(); out.nullValue();
return; return;

View File

@ -18,25 +18,21 @@ package com.google.gson.internal.bind;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import com.google.gson.ToNumberStrategy;
import com.google.gson.ToNumberPolicy; import com.google.gson.ToNumberPolicy;
import com.google.gson.ToNumberStrategy;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory; import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
/** /** Type adapter for {@link Number}. */
* Type adapter for {@link Number}.
*/
public final class NumberTypeAdapter extends TypeAdapter<Number> { public final class NumberTypeAdapter extends TypeAdapter<Number> {
/** /** Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}. */
* Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}. private static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY =
*/ newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER);
private static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY = newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER);
private final ToNumberStrategy toNumberStrategy; private final ToNumberStrategy toNumberStrategy;
@ -48,7 +44,8 @@ public final class NumberTypeAdapter extends TypeAdapter<Number> {
final NumberTypeAdapter adapter = new NumberTypeAdapter(toNumberStrategy); final NumberTypeAdapter adapter = new NumberTypeAdapter(toNumberStrategy);
return new TypeAdapterFactory() { return new TypeAdapterFactory() {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return type.getRawType() == Number.class ? (TypeAdapter<T>) adapter : null; return type.getRawType() == Number.class ? (TypeAdapter<T>) adapter : null;
} }
}; };
@ -62,7 +59,8 @@ public final class NumberTypeAdapter extends TypeAdapter<Number> {
} }
} }
@Override public Number read(JsonReader in) throws IOException { @Override
public Number read(JsonReader in) throws IOException {
JsonToken jsonToken = in.peek(); JsonToken jsonToken = in.peek();
switch (jsonToken) { switch (jsonToken) {
case NULL: case NULL:
@ -72,11 +70,13 @@ public final class NumberTypeAdapter extends TypeAdapter<Number> {
case STRING: case STRING:
return toNumberStrategy.readNumber(in); return toNumberStrategy.readNumber(in);
default: default:
throw new JsonSyntaxException("Expecting number, got: " + jsonToken + "; at path " + in.getPath()); throw new JsonSyntaxException(
"Expecting number, got: " + jsonToken + "; at path " + in.getPath());
} }
} }
@Override public void write(JsonWriter out, Number value) throws IOException { @Override
public void write(JsonWriter out, Number value) throws IOException {
out.value(value); out.value(value);
} }
} }

View File

@ -34,13 +34,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* Adapts types whose static type is only 'Object'. Uses getClass() on * Adapts types whose static type is only 'Object'. Uses getClass() on serialization and a
* serialization and a primitive/Map/List on deserialization. * primitive/Map/List on deserialization.
*/ */
public final class ObjectTypeAdapter extends TypeAdapter<Object> { public final class ObjectTypeAdapter extends TypeAdapter<Object> {
/** /** Gson default factory using {@link ToNumberPolicy#DOUBLE}. */
* Gson default factory using {@link ToNumberPolicy#DOUBLE}.
*/
private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE); private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE);
private final Gson gson; private final Gson gson;
@ -54,7 +52,8 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
private static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) { private static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) {
return new TypeAdapterFactory() { return new TypeAdapterFactory() {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() == Object.class) { if (type.getRawType() == Object.class) {
return (TypeAdapter<T>) new ObjectTypeAdapter(gson, toNumberStrategy); return (TypeAdapter<T>) new ObjectTypeAdapter(gson, toNumberStrategy);
} }
@ -72,8 +71,8 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
} }
/** /**
* Tries to begin reading a JSON array or JSON object, returning {@code null} if * Tries to begin reading a JSON array or JSON object, returning {@code null} if the next element
* the next element is neither of those. * is neither of those.
*/ */
private Object tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException { private Object tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) { switch (peeked) {
@ -106,7 +105,8 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
} }
} }
@Override public Object read(JsonReader in) throws IOException { @Override
public Object read(JsonReader in) throws IOException {
// Either List or Map // Either List or Map
Object current; Object current;
JsonToken peeked = in.peek(); JsonToken peeked = in.peek();
@ -166,7 +166,8 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
} }
} }
@Override public void write(JsonWriter out, Object value) throws IOException { @Override
public void write(JsonWriter out, Object value) throws IOException {
if (value == null) { if (value == null) {
out.nullValue(); out.nullValue();
return; return;

View File

@ -56,9 +56,7 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /** Type adapter that reflects over the fields and methods of a class. */
* Type adapter that reflects over the fields and methods of a class.
*/
public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor; private final ConstructorConstructor constructorConstructor;
private final FieldNamingStrategy fieldNamingPolicy; private final FieldNamingStrategy fieldNamingPolicy;
@ -66,8 +64,10 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory; private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
private final List<ReflectionAccessFilter> reflectionFilters; private final List<ReflectionAccessFilter> reflectionFilters;
public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, public ReflectiveTypeAdapterFactory(
FieldNamingStrategy fieldNamingPolicy, Excluder excluder, ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy,
Excluder excluder,
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory, JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory,
List<ReflectionAccessFilter> reflectionFilters) { List<ReflectionAccessFilter> reflectionFilters) {
this.constructorConstructor = constructorConstructor; this.constructorConstructor = constructorConstructor;
@ -114,36 +114,49 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw); ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
if (filterResult == FilterResult.BLOCK_ALL) { if (filterResult == FilterResult.BLOCK_ALL) {
throw new JsonIOException( throw new JsonIOException(
"ReflectionAccessFilter does not permit using reflection for " + raw "ReflectionAccessFilter does not permit using reflection for "
+ raw
+ ". Register a TypeAdapter for this type or adjust the access filter."); + ". Register a TypeAdapter for this type or adjust the access filter.");
} }
boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE; boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will always be false // If the type is actually a Java Record, we need to use the RecordAdapter instead. This will
// on JVMs that do not support records. // always be false on JVMs that do not support records.
if (ReflectionHelper.isRecord(raw)) { if (ReflectionHelper.isRecord(raw)) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new RecordAdapter<>(raw, TypeAdapter<T> adapter =
getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible); (TypeAdapter<T>)
new RecordAdapter<>(
raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);
return adapter; return adapter;
} }
ObjectConstructor<T> constructor = constructorConstructor.get(type); ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false)); return new FieldReflectionAdapter<>(
constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
} }
private static <M extends AccessibleObject & Member> void checkAccessible(Object object, M member) { private static <M extends AccessibleObject & Member> void checkAccessible(
if (!ReflectionAccessFilterHelper.canAccess(member, Modifier.isStatic(member.getModifiers()) ? null : object)) { Object object, M member) {
if (!ReflectionAccessFilterHelper.canAccess(
member, Modifier.isStatic(member.getModifiers()) ? null : object)) {
String memberDescription = ReflectionHelper.getAccessibleObjectDescription(member, true); String memberDescription = ReflectionHelper.getAccessibleObjectDescription(member, true);
throw new JsonIOException(memberDescription + " is not accessible and ReflectionAccessFilter does not" throw new JsonIOException(
+ " permit making it accessible. Register a TypeAdapter for the declaring type, adjust the" memberDescription
+ " access filter or increase the visibility of the element and its declaring type."); + " is not accessible and ReflectionAccessFilter does not permit making it"
+ " accessible. Register a TypeAdapter for the declaring type, adjust the access"
+ " filter or increase the visibility of the element and its declaring type.");
} }
} }
private BoundField createBoundField( private BoundField createBoundField(
final Gson context, final Field field, final Method accessor, final String serializedName, final Gson context,
final TypeToken<?> fieldType, final boolean serialize, final boolean blockInaccessible) { final Field field,
final Method accessor,
final String serializedName,
final TypeToken<?> fieldType,
final boolean serialize,
final boolean blockInaccessible) {
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType()); final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
@ -154,7 +167,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
TypeAdapter<?> mapped = null; TypeAdapter<?> mapped = null;
if (annotation != null) { if (annotation != null) {
// This is not safe; requires that user has specified correct adapter class for @JsonAdapter // This is not safe; requires that user has specified correct adapter class for @JsonAdapter
mapped = jsonAdapterFactory.getTypeAdapter( mapped =
jsonAdapterFactory.getTypeAdapter(
constructorConstructor, context, fieldType, annotation, false); constructorConstructor, context, fieldType, annotation, false);
} }
final boolean jsonAdapterPresent = mapped != null; final boolean jsonAdapterPresent = mapped != null;
@ -164,15 +178,17 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped; final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
final TypeAdapter<Object> writeTypeAdapter; final TypeAdapter<Object> writeTypeAdapter;
if (serialize) { if (serialize) {
writeTypeAdapter = jsonAdapterPresent ? typeAdapter writeTypeAdapter =
jsonAdapterPresent
? typeAdapter
: new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType()); : new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType());
} else { } else {
// Will never actually be used, but we set it to avoid confusing nullness-analysis tools // Will never actually be used, but we set it to avoid confusing nullness-analysis tools
writeTypeAdapter = typeAdapter; writeTypeAdapter = typeAdapter;
} }
return new BoundField(serializedName, field) { return new BoundField(serializedName, field) {
@Override void write(JsonWriter writer, Object source) @Override
throws IOException, IllegalAccessException { void write(JsonWriter writer, Object source) throws IOException, IllegalAccessException {
if (blockInaccessible) { if (blockInaccessible) {
if (accessor == null) { if (accessor == null) {
checkAccessible(source, field); checkAccessible(source, field);
@ -188,8 +204,10 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
try { try {
fieldValue = accessor.invoke(source); fieldValue = accessor.invoke(source);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
String accessorDescription = ReflectionHelper.getAccessibleObjectDescription(accessor, false); String accessorDescription =
throw new JsonIOException("Accessor " + accessorDescription + " threw exception", e.getCause()); ReflectionHelper.getAccessibleObjectDescription(accessor, false);
throw new JsonIOException(
"Accessor " + accessorDescription + " threw exception", e.getCause());
} }
} else { } else {
fieldValue = field.get(source); fieldValue = field.get(source);
@ -203,11 +221,16 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
} }
@Override @Override
void readIntoArray(JsonReader reader, int index, Object[] target) throws IOException, JsonParseException { void readIntoArray(JsonReader reader, int index, Object[] target)
throws IOException, JsonParseException {
Object fieldValue = typeAdapter.read(reader); Object fieldValue = typeAdapter.read(reader);
if (fieldValue == null && isPrimitive) { if (fieldValue == null && isPrimitive) {
throw new JsonParseException("null is not allowed as value for record component '" + fieldName + "'" throw new JsonParseException(
+ " of primitive type; at path " + reader.getPath()); "null is not allowed as value for record component '"
+ fieldName
+ "'"
+ " of primitive type; at path "
+ reader.getPath());
} }
target[index] = fieldValue; target[index] = fieldValue;
} }
@ -220,8 +243,9 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
if (blockInaccessible) { if (blockInaccessible) {
checkAccessible(target, field); checkAccessible(target, field);
} else if (isStaticFinalField) { } else if (isStaticFinalField) {
// Reflection does not permit setting value of `static final` field, even after calling `setAccessible` // Reflection does not permit setting value of `static final` field, even after calling
// Handle this here to avoid causing IllegalAccessException when calling `Field.set` // `setAccessible` Handle this here to avoid causing IllegalAccessException when calling
// `Field.set`
String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false); String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false);
throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription); throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription);
} }
@ -232,32 +256,47 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
} }
private static class FieldsData { private static class FieldsData {
public static final FieldsData EMPTY = new FieldsData(Collections.<String, BoundField>emptyMap(), Collections.<BoundField>emptyList()); public static final FieldsData EMPTY =
new FieldsData(
Collections.<String, BoundField>emptyMap(), Collections.<BoundField>emptyList());
/** Maps from JSON member name to field */ /** Maps from JSON member name to field */
public final Map<String, BoundField> deserializedFields; public final Map<String, BoundField> deserializedFields;
public final List<BoundField> serializedFields; public final List<BoundField> serializedFields;
public FieldsData(Map<String, BoundField> deserializedFields, List<BoundField> serializedFields) { public FieldsData(
Map<String, BoundField> deserializedFields, List<BoundField> serializedFields) {
this.deserializedFields = deserializedFields; this.deserializedFields = deserializedFields;
this.serializedFields = serializedFields; this.serializedFields = serializedFields;
} }
} }
private static IllegalArgumentException createDuplicateFieldException(Class<?> declaringType, String duplicateName, Field field1, Field field2) { private static IllegalArgumentException createDuplicateFieldException(
throw new IllegalArgumentException("Class " + declaringType.getName() Class<?> declaringType, String duplicateName, Field field1, Field field2) {
+ " declares multiple JSON fields named '" + duplicateName + "'; conflict is caused" throw new IllegalArgumentException(
+ " by fields " + ReflectionHelper.fieldToString(field1) + " and " + ReflectionHelper.fieldToString(field2) "Class "
+ "\nSee " + TroubleshootingGuide.createUrl("duplicate-fields")); + declaringType.getName()
+ " declares multiple JSON fields named '"
+ duplicateName
+ "'; conflict is caused"
+ " by fields "
+ ReflectionHelper.fieldToString(field1)
+ " and "
+ ReflectionHelper.fieldToString(field2)
+ "\nSee "
+ TroubleshootingGuide.createUrl("duplicate-fields"));
} }
private FieldsData getBoundFields(Gson context, TypeToken<?> type, Class<?> raw, boolean blockInaccessible, boolean isRecord) { private FieldsData getBoundFields(
Gson context, TypeToken<?> type, Class<?> raw, boolean blockInaccessible, boolean isRecord) {
if (raw.isInterface()) { if (raw.isInterface()) {
return FieldsData.EMPTY; return FieldsData.EMPTY;
} }
Map<String, BoundField> deserializedFields = new LinkedHashMap<>(); Map<String, BoundField> deserializedFields = new LinkedHashMap<>();
// For serialized fields use a Map to track duplicate field names; otherwise this could be a List<BoundField> instead // For serialized fields use a Map to track duplicate field names; otherwise this could be a
// List<BoundField> instead
Map<String, BoundField> serializedFields = new LinkedHashMap<>(); Map<String, BoundField> serializedFields = new LinkedHashMap<>();
Class<?> originalRaw = raw; Class<?> originalRaw = raw;
@ -266,10 +305,15 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
// For inherited fields, check if access to their declaring class is allowed // For inherited fields, check if access to their declaring class is allowed
if (raw != originalRaw && fields.length > 0) { if (raw != originalRaw && fields.length > 0) {
FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw); FilterResult filterResult =
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
if (filterResult == FilterResult.BLOCK_ALL) { if (filterResult == FilterResult.BLOCK_ALL) {
throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for " + raw throw new JsonIOException(
+ " (supertype of " + originalRaw + "). Register a TypeAdapter for this type" "ReflectionAccessFilter does not permit using reflection for "
+ raw
+ " (supertype of "
+ originalRaw
+ "). Register a TypeAdapter for this type"
+ " or adjust the access filter."); + " or adjust the access filter.");
} }
blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE; blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
@ -281,13 +325,16 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
if (!serialize && !deserialize) { if (!serialize && !deserialize) {
continue; continue;
} }
// The accessor method is only used for records. If the type is a record, we will read out values // The accessor method is only used for records. If the type is a record, we will read out
// via its accessor method instead of via reflection. This way we will bypass the accessible restrictions // values via its accessor method instead of via reflection. This way we will bypass the
// accessible restrictions
Method accessor = null; Method accessor = null;
if (isRecord) { if (isRecord) {
// If there is a static field on a record, there will not be an accessor. Instead we will use the default // If there is a static field on a record, there will not be an accessor. Instead we will
// field serialization logic, but for deserialization the field is excluded for simplicity. Note that Gson // use the default field serialization logic, but for deserialization the field is
// ignores static fields by default, but GsonBuilder.excludeFieldsWithModifiers can overwrite this. // excluded for simplicity.
// Note that Gson ignores static fields by default, but
// GsonBuilder.excludeFieldsWithModifiers can overwrite this.
if (Modifier.isStatic(field.getModifiers())) { if (Modifier.isStatic(field.getModifiers())) {
deserialize = false; deserialize = false;
} else { } else {
@ -298,12 +345,15 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
} }
// @SerializedName can be placed on accessor method, but it is not supported there // @SerializedName can be placed on accessor method, but it is not supported there
// If field and method have annotation it is not easily possible to determine if accessor method // If field and method have annotation it is not easily possible to determine if
// is implicit and has inherited annotation, or if it is explicitly declared with custom annotation // accessor method is implicit and has inherited annotation, or if it is explicitly
// declared with custom annotation
if (accessor.getAnnotation(SerializedName.class) != null if (accessor.getAnnotation(SerializedName.class) != null
&& field.getAnnotation(SerializedName.class) == null) { && field.getAnnotation(SerializedName.class) == null) {
String methodDescription = ReflectionHelper.getAccessibleObjectDescription(accessor, false); String methodDescription =
throw new JsonIOException("@SerializedName on " + methodDescription + " is not supported"); ReflectionHelper.getAccessibleObjectDescription(accessor, false);
throw new JsonIOException(
"@SerializedName on " + methodDescription + " is not supported");
} }
} }
} }
@ -317,8 +367,15 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
List<String> fieldNames = getFieldNames(field); List<String> fieldNames = getFieldNames(field);
String serializedName = fieldNames.get(0); String serializedName = fieldNames.get(0);
BoundField boundField = createBoundField(context, field, accessor, serializedName, BoundField boundField =
TypeToken.get(fieldType), serialize, blockInaccessible); createBoundField(
context,
field,
accessor,
serializedName,
TypeToken.get(fieldType),
serialize,
blockInaccessible);
if (deserialize) { if (deserialize) {
for (String name : fieldNames) { for (String name : fieldNames) {
@ -343,10 +400,12 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
return new FieldsData(deserializedFields, new ArrayList<>(serializedFields.values())); return new FieldsData(deserializedFields, new ArrayList<>(serializedFields.values()));
} }
static abstract class BoundField { abstract static class BoundField {
/** Name used for serialization (but not for deserialization) */ /** Name used for serialization (but not for deserialization) */
final String serializedName; final String serializedName;
final Field field; final Field field;
/** Name of the underlying field */ /** Name of the underlying field */
final String fieldName; final String fieldName;
@ -357,13 +416,19 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
} }
/** Read this field value from the source, and append its JSON value to the writer */ /** Read this field value from the source, and append its JSON value to the writer */
abstract void write(JsonWriter writer, Object source) throws IOException, IllegalAccessException; abstract void write(JsonWriter writer, Object source)
throws IOException, IllegalAccessException;
/** Read the value into the target array, used to provide constructor arguments for records */ /** Read the value into the target array, used to provide constructor arguments for records */
abstract void readIntoArray(JsonReader reader, int index, Object[] target) throws IOException, JsonParseException; abstract void readIntoArray(JsonReader reader, int index, Object[] target)
throws IOException, JsonParseException;
/** Read the value from the reader, and set it on the corresponding field on target via reflection */ /**
abstract void readIntoField(JsonReader reader, Object target) throws IOException, IllegalAccessException; * Read the value from the reader, and set it on the corresponding field on target via
* reflection
*/
abstract void readIntoField(JsonReader reader, Object target)
throws IOException, IllegalAccessException;
} }
/** /**
@ -372,15 +437,16 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
* <p>The {@link RecordAdapter} is a special case to handle records for JVMs that support it, for * <p>The {@link RecordAdapter} is a special case to handle records for JVMs that support it, for
* all other types we use the {@link FieldReflectionAdapter}. This class encapsulates the common * all other types we use the {@link FieldReflectionAdapter}. This class encapsulates the common
* logic for serialization and deserialization. During deserialization, we construct an * logic for serialization and deserialization. During deserialization, we construct an
* accumulator A, which we use to accumulate values from the source JSON. After the object has been read in * accumulator A, which we use to accumulate values from the source JSON. After the object has
* full, the {@link #finalize(Object)} method is used to convert the accumulator to an instance * been read in full, the {@link #finalize(Object)} method is used to convert the accumulator to
* of T. * an instance of T.
* *
* @param <T> type of objects that this Adapter creates. * @param <T> type of objects that this Adapter creates.
* @param <A> type of accumulator used to build the deserialization result. * @param <A> type of accumulator used to build the deserialization result.
*/ */
// This class is public because external projects check for this class with `instanceof` (even though it is internal) // This class is public because external projects check for this class with `instanceof` (even
public static abstract class Adapter<T, A> extends TypeAdapter<T> { // though it is internal)
public abstract static class Adapter<T, A> extends TypeAdapter<T> {
private final FieldsData fieldsData; private final FieldsData fieldsData;
Adapter(FieldsData fieldsData) { Adapter(FieldsData fieldsData) {
@ -437,12 +503,14 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
/** Create the Object that will be used to collect each field value */ /** Create the Object that will be used to collect each field value */
abstract A createAccumulator(); abstract A createAccumulator();
/** /**
* Read a single BoundField into the accumulator. The JsonReader will be pointed at the * Read a single BoundField into the accumulator. The JsonReader will be pointed at the start of
* start of the value for the BoundField to read from. * the value for the BoundField to read from.
*/ */
abstract void readField(A accumulator, JsonReader in, BoundField field) abstract void readField(A accumulator, JsonReader in, BoundField field)
throws IllegalAccessException, IOException; throws IllegalAccessException, IOException;
/** Convert the accumulator to a final instance of T. */ /** Convert the accumulator to a final instance of T. */
abstract T finalize(A accumulator); abstract T finalize(A accumulator);
} }
@ -499,8 +567,9 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
} }
Class<?>[] parameterTypes = constructor.getParameterTypes(); Class<?>[] parameterTypes = constructor.getParameterTypes();
// We need to ensure that we are passing non-null values to primitive fields in the constructor. To do this, // We need to ensure that we are passing non-null values to primitive fields in the
// we create an Object[] where all primitives are initialized to non-null values. // constructor. To do this, we create an Object[] where all primitives are initialized to
// non-null values.
constructorArgsDefaults = new Object[parameterTypes.length]; constructorArgsDefaults = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) { for (int i = 0; i < parameterTypes.length; i++) {
// This will correctly be null for non-primitive types: // This will correctly be null for non-primitive types:
@ -532,12 +601,16 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
Integer componentIndex = componentIndices.get(field.fieldName); Integer componentIndex = componentIndices.get(field.fieldName);
if (componentIndex == null) { if (componentIndex == null) {
throw new IllegalStateException( throw new IllegalStateException(
"Could not find the index in the constructor '" + ReflectionHelper.constructorToString(constructor) + "'" "Could not find the index in the constructor '"
+ " for field with name '" + field.fieldName + "'," + ReflectionHelper.constructorToString(constructor)
+ " unable to determine which argument in the constructor the field corresponds" + "'"
+ " for field with name '"
+ field.fieldName
+ "', unable to determine which argument in the constructor the field corresponds"
+ " to. This is unexpected behavior, as we expect the RecordComponents to have the" + " to. This is unexpected behavior, as we expect the RecordComponents to have the"
+ " same names as the fields in the Java class, and that the order of the" + " same names as the fields in the Java class, and that the order of the"
+ " RecordComponents is the same as the order of the canonical constructor parameters."); + " RecordComponents is the same as the order of the canonical constructor"
+ " parameters.");
} }
field.readIntoArray(in, componentIndex, accumulator); field.readIntoArray(in, componentIndex, accumulator);
} }
@ -550,17 +623,25 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e); throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
} }
// Note: InstantiationException should be impossible because record class is not abstract; // Note: InstantiationException should be impossible because record class is not abstract;
// IllegalArgumentException should not be possible unless a bad adapter returns objects of the wrong type // IllegalArgumentException should not be possible unless a bad adapter returns objects of
// the wrong type
catch (InstantiationException | IllegalArgumentException e) { catch (InstantiationException | IllegalArgumentException e) {
throw new RuntimeException( throw new RuntimeException(
"Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'" "Failed to invoke constructor '"
+ " with args " + Arrays.toString(accumulator), e); + ReflectionHelper.constructorToString(constructor)
} + "'"
catch (InvocationTargetException e) { + " with args "
+ Arrays.toString(accumulator),
e);
} catch (InvocationTargetException e) {
// TODO: JsonParseException ? // TODO: JsonParseException ?
throw new RuntimeException( throw new RuntimeException(
"Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'" "Failed to invoke constructor '"
+ " with args " + Arrays.toString(accumulator), e.getCause()); + ReflectionHelper.constructorToString(constructor)
+ "'"
+ " with args "
+ Arrays.toString(accumulator),
e.getCause());
} }
} }
} }

View File

@ -18,13 +18,11 @@ package com.google.gson.internal.bind;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
/** /** Type adapter which might delegate serialization to another adapter. */
* Type adapter which might delegate serialization to another adapter.
*/
public abstract class SerializationDelegatingTypeAdapter<T> extends TypeAdapter<T> { public abstract class SerializationDelegatingTypeAdapter<T> extends TypeAdapter<T> {
/** /**
* Returns the adapter used for serialization, might be {@code this} or another adapter. * Returns the adapter used for serialization, might be {@code this} or another adapter. That
* That other adapter might itself also be a {@code SerializationDelegatingTypeAdapter}. * other adapter might itself also be a {@code SerializationDelegatingTypeAdapter}.
*/ */
public abstract TypeAdapter<T> getSerializationDelegate(); public abstract TypeAdapter<T> getSerializationDelegate();
} }

View File

@ -34,31 +34,38 @@ import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
/** /**
* Adapts a Gson 1.x tree-style adapter as a streaming TypeAdapter. Since the * Adapts a Gson 1.x tree-style adapter as a streaming TypeAdapter. Since the tree adapter may be
* tree adapter may be serialization-only or deserialization-only, this class * serialization-only or deserialization-only, this class has a facility to look up a delegate type
* has a facility to look up a delegate type adapter on demand. * adapter on demand.
*/ */
public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter<T> { public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter<T> {
private final JsonSerializer<T> serializer; private final JsonSerializer<T> serializer;
private final JsonDeserializer<T> deserializer; private final JsonDeserializer<T> deserializer;
final Gson gson; final Gson gson;
private final TypeToken<T> typeToken; private final TypeToken<T> typeToken;
/** /**
* Only intended as {@code skipPast} for {@link Gson#getDelegateAdapter(TypeAdapterFactory, TypeToken)}, * Only intended as {@code skipPast} for {@link Gson#getDelegateAdapter(TypeAdapterFactory,
* must not be used in any other way. * TypeToken)}, must not be used in any other way.
*/ */
private final TypeAdapterFactory skipPastForGetDelegateAdapter; private final TypeAdapterFactory skipPastForGetDelegateAdapter;
private final GsonContextImpl context = new GsonContextImpl(); private final GsonContextImpl context = new GsonContextImpl();
private final boolean nullSafe; private final boolean nullSafe;
/** /**
* The delegate is lazily created because it may not be needed, and creating it may fail. * The delegate is lazily created because it may not be needed, and creating it may fail. Field
* Field has to be {@code volatile} because {@link Gson} guarantees to be thread-safe. * has to be {@code volatile} because {@link Gson} guarantees to be thread-safe.
*/ */
private volatile TypeAdapter<T> delegate; private volatile TypeAdapter<T> delegate;
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer, public TreeTypeAdapter(
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast, boolean nullSafe) { JsonSerializer<T> serializer,
JsonDeserializer<T> deserializer,
Gson gson,
TypeToken<T> typeToken,
TypeAdapterFactory skipPast,
boolean nullSafe) {
this.serializer = serializer; this.serializer = serializer;
this.deserializer = deserializer; this.deserializer = deserializer;
this.gson = gson; this.gson = gson;
@ -67,12 +74,17 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
this.nullSafe = nullSafe; this.nullSafe = nullSafe;
} }
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer, public TreeTypeAdapter(
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast) { JsonSerializer<T> serializer,
JsonDeserializer<T> deserializer,
Gson gson,
TypeToken<T> typeToken,
TypeAdapterFactory skipPast) {
this(serializer, deserializer, gson, typeToken, skipPast, true); this(serializer, deserializer, gson, typeToken, skipPast, true);
} }
@Override public T read(JsonReader in) throws IOException { @Override
public T read(JsonReader in) throws IOException {
if (deserializer == null) { if (deserializer == null) {
return delegate().read(in); return delegate().read(in);
} }
@ -83,7 +95,8 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
return deserializer.deserialize(value, typeToken.getType(), context); return deserializer.deserialize(value, typeToken.getType(), context);
} }
@Override public void write(JsonWriter out, T value) throws IOException { @Override
public void write(JsonWriter out, T value) throws IOException {
if (serializer == null) { if (serializer == null) {
delegate().write(out, value); delegate().write(out, value);
return; return;
@ -97,7 +110,8 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
} }
private TypeAdapter<T> delegate() { private TypeAdapter<T> delegate() {
// A race might lead to `delegate` being assigned by multiple threads but the last assignment will stick // A race might lead to `delegate` being assigned by multiple threads but the last assignment
// will stick
TypeAdapter<T> d = delegate; TypeAdapter<T> d = delegate;
return d != null return d != null
? d ? d
@ -105,25 +119,20 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
} }
/** /**
* Returns the type adapter which is used for serialization. Returns {@code this} * Returns the type adapter which is used for serialization. Returns {@code this} if this {@code
* if this {@code TreeTypeAdapter} has a {@link #serializer}; otherwise returns * TreeTypeAdapter} has a {@link #serializer}; otherwise returns the delegate.
* the delegate.
*/ */
@Override public TypeAdapter<T> getSerializationDelegate() { @Override
public TypeAdapter<T> getSerializationDelegate() {
return serializer != null ? this : delegate(); return serializer != null ? this : delegate();
} }
/** /** Returns a new factory that will match each type against {@code exactType}. */
* Returns a new factory that will match each type against {@code exactType}.
*/
public static TypeAdapterFactory newFactory(TypeToken<?> exactType, Object typeAdapter) { public static TypeAdapterFactory newFactory(TypeToken<?> exactType, Object typeAdapter) {
return new SingleTypeFactory(typeAdapter, exactType, false, null); return new SingleTypeFactory(typeAdapter, exactType, false, null);
} }
/** /** Returns a new factory that will match each type and its raw type against {@code exactType}. */
* Returns a new factory that will match each type and its raw type against
* {@code exactType}.
*/
public static TypeAdapterFactory newFactoryWithMatchRawType( public static TypeAdapterFactory newFactoryWithMatchRawType(
TypeToken<?> exactType, Object typeAdapter) { TypeToken<?> exactType, Object typeAdapter) {
// only bother matching raw types if exact type is a raw type // only bother matching raw types if exact type is a raw type
@ -132,8 +141,8 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
} }
/** /**
* Returns a new factory that will match each type's raw type for assignability * Returns a new factory that will match each type's raw type for assignability to {@code
* to {@code hierarchyType}. * hierarchyType}.
*/ */
public static TypeAdapterFactory newTypeHierarchyFactory( public static TypeAdapterFactory newTypeHierarchyFactory(
Class<?> hierarchyType, Object typeAdapter) { Class<?> hierarchyType, Object typeAdapter) {
@ -147,14 +156,11 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
private final JsonSerializer<?> serializer; private final JsonSerializer<?> serializer;
private final JsonDeserializer<?> deserializer; private final JsonDeserializer<?> deserializer;
SingleTypeFactory(Object typeAdapter, TypeToken<?> exactType, boolean matchRawType, SingleTypeFactory(
Class<?> hierarchyType) { Object typeAdapter, TypeToken<?> exactType, boolean matchRawType, Class<?> hierarchyType) {
serializer = typeAdapter instanceof JsonSerializer serializer = typeAdapter instanceof JsonSerializer ? (JsonSerializer<?>) typeAdapter : null;
? (JsonSerializer<?>) typeAdapter deserializer =
: null; typeAdapter instanceof JsonDeserializer ? (JsonDeserializer<?>) typeAdapter : null;
deserializer = typeAdapter instanceof JsonDeserializer
? (JsonDeserializer<?>) typeAdapter
: null;
$Gson$Preconditions.checkArgument(serializer != null || deserializer != null); $Gson$Preconditions.checkArgument(serializer != null || deserializer != null);
this.exactType = exactType; this.exactType = exactType;
this.matchRawType = matchRawType; this.matchRawType = matchRawType;
@ -164,23 +170,29 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
@SuppressWarnings("unchecked") // guarded by typeToken.equals() call @SuppressWarnings("unchecked") // guarded by typeToken.equals() call
@Override @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
boolean matches = exactType != null boolean matches =
exactType != null
? exactType.equals(type) || (matchRawType && exactType.getType() == type.getRawType()) ? exactType.equals(type) || (matchRawType && exactType.getType() == type.getRawType())
: hierarchyType.isAssignableFrom(type.getRawType()); : hierarchyType.isAssignableFrom(type.getRawType());
return matches return matches
? new TreeTypeAdapter<>((JsonSerializer<T>) serializer, ? new TreeTypeAdapter<>(
(JsonDeserializer<T>) deserializer, gson, type, this) (JsonSerializer<T>) serializer, (JsonDeserializer<T>) deserializer, gson, type, this)
: null; : null;
} }
} }
private final class GsonContextImpl implements JsonSerializationContext, JsonDeserializationContext { private final class GsonContextImpl
@Override public JsonElement serialize(Object src) { implements JsonSerializationContext, JsonDeserializationContext {
@Override
public JsonElement serialize(Object src) {
return gson.toJsonTree(src); return gson.toJsonTree(src);
} }
@Override public JsonElement serialize(Object src, Type typeOfSrc) {
@Override
public JsonElement serialize(Object src, Type typeOfSrc) {
return gson.toJsonTree(src, typeOfSrc); return gson.toJsonTree(src, typeOfSrc);
} }
@Override @Override
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
public <R> R deserialize(JsonElement json, Type typeOfT) throws JsonParseException { public <R> R deserialize(JsonElement json, Type typeOfT) throws JsonParseException {

View File

@ -45,16 +45,19 @@ final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
// Order of preference for choosing type adapters // Order of preference for choosing type adapters
// First preference: a type adapter registered for the runtime type // First preference: a type adapter registered for the runtime type
// Second preference: a type adapter registered for the declared type // Second preference: a type adapter registered for the declared type
// Third preference: reflective type adapter for the runtime type (if it is a subclass of the declared type) // Third preference: reflective type adapter for the runtime type (if it is a subclass of the
// declared type)
// Fourth preference: reflective type adapter for the declared type // Fourth preference: reflective type adapter for the declared type
TypeAdapter<T> chosen = delegate; TypeAdapter<T> chosen = delegate;
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value); Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
if (runtimeType != type) { if (runtimeType != type) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
TypeAdapter<T> runtimeTypeAdapter = (TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType)); TypeAdapter<T> runtimeTypeAdapter =
// For backward compatibility only check ReflectiveTypeAdapterFactory.Adapter here but not any other (TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType));
// wrapping adapters, see https://github.com/google/gson/pull/1787#issuecomment-1222175189 // For backward compatibility only check ReflectiveTypeAdapterFactory.Adapter here but not any
// other wrapping adapters, see
// https://github.com/google/gson/pull/1787#issuecomment-1222175189
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) { if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for the runtime type, so we will use that // The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter; chosen = runtimeTypeAdapter;
@ -78,7 +81,8 @@ final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
private static boolean isReflective(TypeAdapter<?> typeAdapter) { private static boolean isReflective(TypeAdapter<?> typeAdapter) {
// Run this in loop in case multiple delegating adapters are nested // Run this in loop in case multiple delegating adapters are nested
while (typeAdapter instanceof SerializationDelegatingTypeAdapter) { while (typeAdapter instanceof SerializationDelegatingTypeAdapter) {
TypeAdapter<?> delegate = ((SerializationDelegatingTypeAdapter<?>) typeAdapter).getSerializationDelegate(); TypeAdapter<?> delegate =
((SerializationDelegatingTypeAdapter<?>) typeAdapter).getSerializationDelegate();
// Break if adapter does not delegate serialization // Break if adapter does not delegate serialization
if (delegate == typeAdapter) { if (delegate == typeAdapter) {
break; break;
@ -89,9 +93,7 @@ final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
return typeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter; return typeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter;
} }
/** /** Finds a compatible runtime type if it is more specific */
* Finds a compatible runtime type if it is more specific
*/
private static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) { private static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
if (value != null && (type instanceof Class<?> || type instanceof TypeVariable<?>)) { if (value != null && (type instanceof Class<?> || type instanceof TypeVariable<?>)) {
type = value.getClass(); type = value.getClass();

View File

@ -62,34 +62,40 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicIntegerArray;
/** /** Type adapters for basic types. */
* Type adapters for basic types.
*/
public final class TypeAdapters { public final class TypeAdapters {
private TypeAdapters() { private TypeAdapters() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public static final TypeAdapter<Class> CLASS = new TypeAdapter<Class>() { public static final TypeAdapter<Class> CLASS =
new TypeAdapter<Class>() {
@Override @Override
public void write(JsonWriter out, Class value) throws IOException { public void write(JsonWriter out, Class value) throws IOException {
throw new UnsupportedOperationException("Attempted to serialize java.lang.Class: " throw new UnsupportedOperationException(
+ value.getName() + ". Forgot to register a type adapter?" "Attempted to serialize java.lang.Class: "
+ "\nSee " + TroubleshootingGuide.createUrl("java-lang-class-unsupported")); + value.getName()
+ ". Forgot to register a type adapter?"
+ "\nSee "
+ TroubleshootingGuide.createUrl("java-lang-class-unsupported"));
} }
@Override @Override
public Class read(JsonReader in) throws IOException { public Class read(JsonReader in) throws IOException {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?" "Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?"
+ "\nSee " + TroubleshootingGuide.createUrl("java-lang-class-unsupported")); + "\nSee "
+ TroubleshootingGuide.createUrl("java-lang-class-unsupported"));
} }
}.nullSafe(); }.nullSafe();
public static final TypeAdapterFactory CLASS_FACTORY = newFactory(Class.class, CLASS); public static final TypeAdapterFactory CLASS_FACTORY = newFactory(Class.class, CLASS);
public static final TypeAdapter<BitSet> BIT_SET = new TypeAdapter<BitSet>() { public static final TypeAdapter<BitSet> BIT_SET =
@Override public BitSet read(JsonReader in) throws IOException { new TypeAdapter<BitSet>() {
@Override
public BitSet read(JsonReader in) throws IOException {
BitSet bitset = new BitSet(); BitSet bitset = new BitSet();
in.beginArray(); in.beginArray();
int i = 0; int i = 0;
@ -105,14 +111,19 @@ public final class TypeAdapters {
} else if (intValue == 1) { } else if (intValue == 1) {
set = true; set = true;
} else { } else {
throw new JsonSyntaxException("Invalid bitset value " + intValue + ", expected 0 or 1; at path " + in.getPreviousPath()); throw new JsonSyntaxException(
"Invalid bitset value "
+ intValue
+ ", expected 0 or 1; at path "
+ in.getPreviousPath());
} }
break; break;
case BOOLEAN: case BOOLEAN:
set = in.nextBoolean(); set = in.nextBoolean();
break; break;
default: default:
throw new JsonSyntaxException("Invalid bitset value type: " + tokenType + "; at path " + in.getPath()); throw new JsonSyntaxException(
"Invalid bitset value type: " + tokenType + "; at path " + in.getPath());
} }
if (set) { if (set) {
bitset.set(i); bitset.set(i);
@ -124,7 +135,8 @@ public final class TypeAdapters {
return bitset; return bitset;
} }
@Override public void write(JsonWriter out, BitSet src) throws IOException { @Override
public void write(JsonWriter out, BitSet src) throws IOException {
out.beginArray(); out.beginArray();
for (int i = 0, length = src.length(); i < length; i++) { for (int i = 0, length = src.length(); i < length; i++) {
int value = src.get(i) ? 1 : 0; int value = src.get(i) ? 1 : 0;
@ -136,7 +148,8 @@ public final class TypeAdapters {
public static final TypeAdapterFactory BIT_SET_FACTORY = newFactory(BitSet.class, BIT_SET); public static final TypeAdapterFactory BIT_SET_FACTORY = newFactory(BitSet.class, BIT_SET);
public static final TypeAdapter<Boolean> BOOLEAN = new TypeAdapter<Boolean>() { public static final TypeAdapter<Boolean> BOOLEAN =
new TypeAdapter<Boolean>() {
@Override @Override
public Boolean read(JsonReader in) throws IOException { public Boolean read(JsonReader in) throws IOException {
JsonToken peek = in.peek(); JsonToken peek = in.peek();
@ -149,6 +162,7 @@ public final class TypeAdapters {
} }
return in.nextBoolean(); return in.nextBoolean();
} }
@Override @Override
public void write(JsonWriter out, Boolean value) throws IOException { public void write(JsonWriter out, Boolean value) throws IOException {
out.value(value); out.value(value);
@ -156,11 +170,12 @@ public final class TypeAdapters {
}; };
/** /**
* Writes a boolean as a string. Useful for map keys, where booleans aren't * Writes a boolean as a string. Useful for map keys, where booleans aren't otherwise permitted.
* otherwise permitted.
*/ */
public static final TypeAdapter<Boolean> BOOLEAN_AS_STRING = new TypeAdapter<Boolean>() { public static final TypeAdapter<Boolean> BOOLEAN_AS_STRING =
@Override public Boolean read(JsonReader in) throws IOException { new TypeAdapter<Boolean>() {
@Override
public Boolean read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -168,15 +183,17 @@ public final class TypeAdapters {
return Boolean.valueOf(in.nextString()); return Boolean.valueOf(in.nextString());
} }
@Override public void write(JsonWriter out, Boolean value) throws IOException { @Override
public void write(JsonWriter out, Boolean value) throws IOException {
out.value(value == null ? "null" : value.toString()); out.value(value == null ? "null" : value.toString());
} }
}; };
public static final TypeAdapterFactory BOOLEAN_FACTORY public static final TypeAdapterFactory BOOLEAN_FACTORY =
= newFactory(boolean.class, Boolean.class, BOOLEAN); newFactory(boolean.class, Boolean.class, BOOLEAN);
public static final TypeAdapter<Number> BYTE = new TypeAdapter<Number>() { public static final TypeAdapter<Number> BYTE =
new TypeAdapter<Number>() {
@Override @Override
public Number read(JsonReader in) throws IOException { public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -192,10 +209,12 @@ public final class TypeAdapters {
} }
// Allow up to 255 to support unsigned values // Allow up to 255 to support unsigned values
if (intValue > 255 || intValue < Byte.MIN_VALUE) { if (intValue > 255 || intValue < Byte.MIN_VALUE) {
throw new JsonSyntaxException("Lossy conversion from " + intValue + " to byte; at path " + in.getPreviousPath()); throw new JsonSyntaxException(
"Lossy conversion from " + intValue + " to byte; at path " + in.getPreviousPath());
} }
return (byte) intValue; return (byte) intValue;
} }
@Override @Override
public void write(JsonWriter out, Number value) throws IOException { public void write(JsonWriter out, Number value) throws IOException {
if (value == null) { if (value == null) {
@ -206,10 +225,10 @@ public final class TypeAdapters {
} }
}; };
public static final TypeAdapterFactory BYTE_FACTORY public static final TypeAdapterFactory BYTE_FACTORY = newFactory(byte.class, Byte.class, BYTE);
= newFactory(byte.class, Byte.class, BYTE);
public static final TypeAdapter<Number> SHORT = new TypeAdapter<Number>() { public static final TypeAdapter<Number> SHORT =
new TypeAdapter<Number>() {
@Override @Override
public Number read(JsonReader in) throws IOException { public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -225,10 +244,12 @@ public final class TypeAdapters {
} }
// Allow up to 65535 to support unsigned values // Allow up to 65535 to support unsigned values
if (intValue > 65535 || intValue < Short.MIN_VALUE) { if (intValue > 65535 || intValue < Short.MIN_VALUE) {
throw new JsonSyntaxException("Lossy conversion from " + intValue + " to short; at path " + in.getPreviousPath()); throw new JsonSyntaxException(
"Lossy conversion from " + intValue + " to short; at path " + in.getPreviousPath());
} }
return (short) intValue; return (short) intValue;
} }
@Override @Override
public void write(JsonWriter out, Number value) throws IOException { public void write(JsonWriter out, Number value) throws IOException {
if (value == null) { if (value == null) {
@ -239,10 +260,11 @@ public final class TypeAdapters {
} }
}; };
public static final TypeAdapterFactory SHORT_FACTORY public static final TypeAdapterFactory SHORT_FACTORY =
= newFactory(short.class, Short.class, SHORT); newFactory(short.class, Short.class, SHORT);
public static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() { public static final TypeAdapter<Number> INTEGER =
new TypeAdapter<Number>() {
@Override @Override
public Number read(JsonReader in) throws IOException { public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -255,6 +277,7 @@ public final class TypeAdapters {
throw new JsonSyntaxException(e); throw new JsonSyntaxException(e);
} }
} }
@Override @Override
public void write(JsonWriter out, Number value) throws IOException { public void write(JsonWriter out, Number value) throws IOException {
if (value == null) { if (value == null) {
@ -264,37 +287,47 @@ public final class TypeAdapters {
} }
} }
}; };
public static final TypeAdapterFactory INTEGER_FACTORY public static final TypeAdapterFactory INTEGER_FACTORY =
= newFactory(int.class, Integer.class, INTEGER); newFactory(int.class, Integer.class, INTEGER);
public static final TypeAdapter<AtomicInteger> ATOMIC_INTEGER = new TypeAdapter<AtomicInteger>() { public static final TypeAdapter<AtomicInteger> ATOMIC_INTEGER =
@Override public AtomicInteger read(JsonReader in) throws IOException { new TypeAdapter<AtomicInteger>() {
@Override
public AtomicInteger read(JsonReader in) throws IOException {
try { try {
return new AtomicInteger(in.nextInt()); return new AtomicInteger(in.nextInt());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonSyntaxException(e); throw new JsonSyntaxException(e);
} }
} }
@Override public void write(JsonWriter out, AtomicInteger value) throws IOException {
@Override
public void write(JsonWriter out, AtomicInteger value) throws IOException {
out.value(value.get()); out.value(value.get());
} }
}.nullSafe(); }.nullSafe();
public static final TypeAdapterFactory ATOMIC_INTEGER_FACTORY = public static final TypeAdapterFactory ATOMIC_INTEGER_FACTORY =
newFactory(AtomicInteger.class, TypeAdapters.ATOMIC_INTEGER); newFactory(AtomicInteger.class, TypeAdapters.ATOMIC_INTEGER);
public static final TypeAdapter<AtomicBoolean> ATOMIC_BOOLEAN = new TypeAdapter<AtomicBoolean>() { public static final TypeAdapter<AtomicBoolean> ATOMIC_BOOLEAN =
@Override public AtomicBoolean read(JsonReader in) throws IOException { new TypeAdapter<AtomicBoolean>() {
@Override
public AtomicBoolean read(JsonReader in) throws IOException {
return new AtomicBoolean(in.nextBoolean()); return new AtomicBoolean(in.nextBoolean());
} }
@Override public void write(JsonWriter out, AtomicBoolean value) throws IOException {
@Override
public void write(JsonWriter out, AtomicBoolean value) throws IOException {
out.value(value.get()); out.value(value.get());
} }
}.nullSafe(); }.nullSafe();
public static final TypeAdapterFactory ATOMIC_BOOLEAN_FACTORY = public static final TypeAdapterFactory ATOMIC_BOOLEAN_FACTORY =
newFactory(AtomicBoolean.class, TypeAdapters.ATOMIC_BOOLEAN); newFactory(AtomicBoolean.class, TypeAdapters.ATOMIC_BOOLEAN);
public static final TypeAdapter<AtomicIntegerArray> ATOMIC_INTEGER_ARRAY = new TypeAdapter<AtomicIntegerArray>() { public static final TypeAdapter<AtomicIntegerArray> ATOMIC_INTEGER_ARRAY =
@Override public AtomicIntegerArray read(JsonReader in) throws IOException { new TypeAdapter<AtomicIntegerArray>() {
@Override
public AtomicIntegerArray read(JsonReader in) throws IOException {
List<Integer> list = new ArrayList<>(); List<Integer> list = new ArrayList<>();
in.beginArray(); in.beginArray();
while (in.hasNext()) { while (in.hasNext()) {
@ -313,7 +346,9 @@ public final class TypeAdapters {
} }
return array; return array;
} }
@Override public void write(JsonWriter out, AtomicIntegerArray value) throws IOException {
@Override
public void write(JsonWriter out, AtomicIntegerArray value) throws IOException {
out.beginArray(); out.beginArray();
for (int i = 0, length = value.length(); i < length; i++) { for (int i = 0, length = value.length(); i < length; i++) {
out.value(value.get(i)); out.value(value.get(i));
@ -324,7 +359,8 @@ public final class TypeAdapters {
public static final TypeAdapterFactory ATOMIC_INTEGER_ARRAY_FACTORY = public static final TypeAdapterFactory ATOMIC_INTEGER_ARRAY_FACTORY =
newFactory(AtomicIntegerArray.class, TypeAdapters.ATOMIC_INTEGER_ARRAY); newFactory(AtomicIntegerArray.class, TypeAdapters.ATOMIC_INTEGER_ARRAY);
public static final TypeAdapter<Number> LONG = new TypeAdapter<Number>() { public static final TypeAdapter<Number> LONG =
new TypeAdapter<Number>() {
@Override @Override
public Number read(JsonReader in) throws IOException { public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -337,6 +373,7 @@ public final class TypeAdapters {
throw new JsonSyntaxException(e); throw new JsonSyntaxException(e);
} }
} }
@Override @Override
public void write(JsonWriter out, Number value) throws IOException { public void write(JsonWriter out, Number value) throws IOException {
if (value == null) { if (value == null) {
@ -347,7 +384,8 @@ public final class TypeAdapters {
} }
}; };
public static final TypeAdapter<Number> FLOAT = new TypeAdapter<Number>() { public static final TypeAdapter<Number> FLOAT =
new TypeAdapter<Number>() {
@Override @Override
public Number read(JsonReader in) throws IOException { public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -356,12 +394,14 @@ public final class TypeAdapters {
} }
return (float) in.nextDouble(); return (float) in.nextDouble();
} }
@Override @Override
public void write(JsonWriter out, Number value) throws IOException { public void write(JsonWriter out, Number value) throws IOException {
if (value == null) { if (value == null) {
out.nullValue(); out.nullValue();
} else { } else {
// For backward compatibility don't call `JsonWriter.value(float)` because that method has // For backward compatibility don't call `JsonWriter.value(float)` because that method
// has
// been newly added and not all custom JsonWriter implementations might override it yet // been newly added and not all custom JsonWriter implementations might override it yet
Number floatNumber = value instanceof Float ? value : value.floatValue(); Number floatNumber = value instanceof Float ? value : value.floatValue();
out.value(floatNumber); out.value(floatNumber);
@ -369,7 +409,8 @@ public final class TypeAdapters {
} }
}; };
public static final TypeAdapter<Number> DOUBLE = new TypeAdapter<Number>() { public static final TypeAdapter<Number> DOUBLE =
new TypeAdapter<Number>() {
@Override @Override
public Number read(JsonReader in) throws IOException { public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -378,6 +419,7 @@ public final class TypeAdapters {
} }
return in.nextDouble(); return in.nextDouble();
} }
@Override @Override
public void write(JsonWriter out, Number value) throws IOException { public void write(JsonWriter out, Number value) throws IOException {
if (value == null) { if (value == null) {
@ -388,7 +430,8 @@ public final class TypeAdapters {
} }
}; };
public static final TypeAdapter<Character> CHARACTER = new TypeAdapter<Character>() { public static final TypeAdapter<Character> CHARACTER =
new TypeAdapter<Character>() {
@Override @Override
public Character read(JsonReader in) throws IOException { public Character read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -397,20 +440,23 @@ public final class TypeAdapters {
} }
String str = in.nextString(); String str = in.nextString();
if (str.length() != 1) { if (str.length() != 1) {
throw new JsonSyntaxException("Expecting character, got: " + str + "; at " + in.getPreviousPath()); throw new JsonSyntaxException(
"Expecting character, got: " + str + "; at " + in.getPreviousPath());
} }
return str.charAt(0); return str.charAt(0);
} }
@Override @Override
public void write(JsonWriter out, Character value) throws IOException { public void write(JsonWriter out, Character value) throws IOException {
out.value(value == null ? null : String.valueOf(value)); out.value(value == null ? null : String.valueOf(value));
} }
}; };
public static final TypeAdapterFactory CHARACTER_FACTORY public static final TypeAdapterFactory CHARACTER_FACTORY =
= newFactory(char.class, Character.class, CHARACTER); newFactory(char.class, Character.class, CHARACTER);
public static final TypeAdapter<String> STRING = new TypeAdapter<String>() { public static final TypeAdapter<String> STRING =
new TypeAdapter<String>() {
@Override @Override
public String read(JsonReader in) throws IOException { public String read(JsonReader in) throws IOException {
JsonToken peek = in.peek(); JsonToken peek = in.peek();
@ -424,14 +470,17 @@ public final class TypeAdapters {
} }
return in.nextString(); return in.nextString();
} }
@Override @Override
public void write(JsonWriter out, String value) throws IOException { public void write(JsonWriter out, String value) throws IOException {
out.value(value); out.value(value);
} }
}; };
public static final TypeAdapter<BigDecimal> BIG_DECIMAL = new TypeAdapter<BigDecimal>() { public static final TypeAdapter<BigDecimal> BIG_DECIMAL =
@Override public BigDecimal read(JsonReader in) throws IOException { new TypeAdapter<BigDecimal>() {
@Override
public BigDecimal read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -440,17 +489,21 @@ public final class TypeAdapters {
try { try {
return NumberLimits.parseBigDecimal(s); return NumberLimits.parseBigDecimal(s);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as BigDecimal; at path " + in.getPreviousPath(), e); throw new JsonSyntaxException(
"Failed parsing '" + s + "' as BigDecimal; at path " + in.getPreviousPath(), e);
} }
} }
@Override public void write(JsonWriter out, BigDecimal value) throws IOException { @Override
public void write(JsonWriter out, BigDecimal value) throws IOException {
out.value(value); out.value(value);
} }
}; };
public static final TypeAdapter<BigInteger> BIG_INTEGER = new TypeAdapter<BigInteger>() { public static final TypeAdapter<BigInteger> BIG_INTEGER =
@Override public BigInteger read(JsonReader in) throws IOException { new TypeAdapter<BigInteger>() {
@Override
public BigInteger read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -459,20 +512,24 @@ public final class TypeAdapters {
try { try {
return NumberLimits.parseBigInteger(s); return NumberLimits.parseBigInteger(s);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as BigInteger; at path " + in.getPreviousPath(), e); throw new JsonSyntaxException(
"Failed parsing '" + s + "' as BigInteger; at path " + in.getPreviousPath(), e);
} }
} }
@Override public void write(JsonWriter out, BigInteger value) throws IOException { @Override
public void write(JsonWriter out, BigInteger value) throws IOException {
out.value(value); out.value(value);
} }
}; };
public static final TypeAdapter<LazilyParsedNumber> LAZILY_PARSED_NUMBER = new TypeAdapter<LazilyParsedNumber>() { public static final TypeAdapter<LazilyParsedNumber> LAZILY_PARSED_NUMBER =
new TypeAdapter<LazilyParsedNumber>() {
// Normally users should not be able to access and deserialize LazilyParsedNumber because // Normally users should not be able to access and deserialize LazilyParsedNumber because
// it is an internal type, but implement this nonetheless in case there are legit corner // it is an internal type, but implement this nonetheless in case there are legit corner
// cases where this is possible // cases where this is possible
@Override public LazilyParsedNumber read(JsonReader in) throws IOException { @Override
public LazilyParsedNumber read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -480,14 +537,16 @@ public final class TypeAdapters {
return new LazilyParsedNumber(in.nextString()); return new LazilyParsedNumber(in.nextString());
} }
@Override public void write(JsonWriter out, LazilyParsedNumber value) throws IOException { @Override
public void write(JsonWriter out, LazilyParsedNumber value) throws IOException {
out.value(value); out.value(value);
} }
}; };
public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING); public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING);
public static final TypeAdapter<StringBuilder> STRING_BUILDER = new TypeAdapter<StringBuilder>() { public static final TypeAdapter<StringBuilder> STRING_BUILDER =
new TypeAdapter<StringBuilder>() {
@Override @Override
public StringBuilder read(JsonReader in) throws IOException { public StringBuilder read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -496,6 +555,7 @@ public final class TypeAdapters {
} }
return new StringBuilder(in.nextString()); return new StringBuilder(in.nextString());
} }
@Override @Override
public void write(JsonWriter out, StringBuilder value) throws IOException { public void write(JsonWriter out, StringBuilder value) throws IOException {
out.value(value == null ? null : value.toString()); out.value(value == null ? null : value.toString());
@ -505,7 +565,8 @@ public final class TypeAdapters {
public static final TypeAdapterFactory STRING_BUILDER_FACTORY = public static final TypeAdapterFactory STRING_BUILDER_FACTORY =
newFactory(StringBuilder.class, STRING_BUILDER); newFactory(StringBuilder.class, STRING_BUILDER);
public static final TypeAdapter<StringBuffer> STRING_BUFFER = new TypeAdapter<StringBuffer>() { public static final TypeAdapter<StringBuffer> STRING_BUFFER =
new TypeAdapter<StringBuffer>() {
@Override @Override
public StringBuffer read(JsonReader in) throws IOException { public StringBuffer read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -514,6 +575,7 @@ public final class TypeAdapters {
} }
return new StringBuffer(in.nextString()); return new StringBuffer(in.nextString());
} }
@Override @Override
public void write(JsonWriter out, StringBuffer value) throws IOException { public void write(JsonWriter out, StringBuffer value) throws IOException {
out.value(value == null ? null : value.toString()); out.value(value == null ? null : value.toString());
@ -523,7 +585,8 @@ public final class TypeAdapters {
public static final TypeAdapterFactory STRING_BUFFER_FACTORY = public static final TypeAdapterFactory STRING_BUFFER_FACTORY =
newFactory(StringBuffer.class, STRING_BUFFER); newFactory(StringBuffer.class, STRING_BUFFER);
public static final TypeAdapter<URL> URL = new TypeAdapter<URL>() { public static final TypeAdapter<URL> URL =
new TypeAdapter<URL>() {
@Override @Override
public URL read(JsonReader in) throws IOException { public URL read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -533,6 +596,7 @@ public final class TypeAdapters {
String nextString = in.nextString(); String nextString = in.nextString();
return "null".equals(nextString) ? null : new URL(nextString); return "null".equals(nextString) ? null : new URL(nextString);
} }
@Override @Override
public void write(JsonWriter out, URL value) throws IOException { public void write(JsonWriter out, URL value) throws IOException {
out.value(value == null ? null : value.toExternalForm()); out.value(value == null ? null : value.toExternalForm());
@ -541,7 +605,8 @@ public final class TypeAdapters {
public static final TypeAdapterFactory URL_FACTORY = newFactory(URL.class, URL); public static final TypeAdapterFactory URL_FACTORY = newFactory(URL.class, URL);
public static final TypeAdapter<URI> URI = new TypeAdapter<URI>() { public static final TypeAdapter<URI> URI =
new TypeAdapter<URI>() {
@Override @Override
public URI read(JsonReader in) throws IOException { public URI read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -555,6 +620,7 @@ public final class TypeAdapters {
throw new JsonIOException(e); throw new JsonIOException(e);
} }
} }
@Override @Override
public void write(JsonWriter out, URI value) throws IOException { public void write(JsonWriter out, URI value) throws IOException {
out.value(value == null ? null : value.toASCIIString()); out.value(value == null ? null : value.toASCIIString());
@ -563,7 +629,8 @@ public final class TypeAdapters {
public static final TypeAdapterFactory URI_FACTORY = newFactory(URI.class, URI); public static final TypeAdapterFactory URI_FACTORY = newFactory(URI.class, URI);
public static final TypeAdapter<InetAddress> INET_ADDRESS = new TypeAdapter<InetAddress>() { public static final TypeAdapter<InetAddress> INET_ADDRESS =
new TypeAdapter<InetAddress>() {
@Override @Override
public InetAddress read(JsonReader in) throws IOException { public InetAddress read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -577,6 +644,7 @@ public final class TypeAdapters {
InetAddress addr = InetAddress.getByName(in.nextString()); InetAddress addr = InetAddress.getByName(in.nextString());
return addr; return addr;
} }
@Override @Override
public void write(JsonWriter out, InetAddress value) throws IOException { public void write(JsonWriter out, InetAddress value) throws IOException {
out.value(value == null ? null : value.getHostAddress()); out.value(value == null ? null : value.getHostAddress());
@ -586,7 +654,8 @@ public final class TypeAdapters {
public static final TypeAdapterFactory INET_ADDRESS_FACTORY = public static final TypeAdapterFactory INET_ADDRESS_FACTORY =
newTypeHierarchyFactory(InetAddress.class, INET_ADDRESS); newTypeHierarchyFactory(InetAddress.class, INET_ADDRESS);
public static final TypeAdapter<UUID> UUID = new TypeAdapter<UUID>() { public static final TypeAdapter<UUID> UUID =
new TypeAdapter<UUID>() {
@Override @Override
public UUID read(JsonReader in) throws IOException { public UUID read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -597,9 +666,11 @@ public final class TypeAdapters {
try { try {
return java.util.UUID.fromString(s); return java.util.UUID.fromString(s);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as UUID; at path " + in.getPreviousPath(), e); throw new JsonSyntaxException(
"Failed parsing '" + s + "' as UUID; at path " + in.getPreviousPath(), e);
} }
} }
@Override @Override
public void write(JsonWriter out, UUID value) throws IOException { public void write(JsonWriter out, UUID value) throws IOException {
out.value(value == null ? null : value.toString()); out.value(value == null ? null : value.toString());
@ -608,16 +679,19 @@ public final class TypeAdapters {
public static final TypeAdapterFactory UUID_FACTORY = newFactory(UUID.class, UUID); public static final TypeAdapterFactory UUID_FACTORY = newFactory(UUID.class, UUID);
public static final TypeAdapter<Currency> CURRENCY = new TypeAdapter<Currency>() { public static final TypeAdapter<Currency> CURRENCY =
new TypeAdapter<Currency>() {
@Override @Override
public Currency read(JsonReader in) throws IOException { public Currency read(JsonReader in) throws IOException {
String s = in.nextString(); String s = in.nextString();
try { try {
return Currency.getInstance(s); return Currency.getInstance(s);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as Currency; at path " + in.getPreviousPath(), e); throw new JsonSyntaxException(
"Failed parsing '" + s + "' as Currency; at path " + in.getPreviousPath(), e);
} }
} }
@Override @Override
public void write(JsonWriter out, Currency value) throws IOException { public void write(JsonWriter out, Currency value) throws IOException {
out.value(value.getCurrencyCode()); out.value(value.getCurrencyCode());
@ -625,7 +699,8 @@ public final class TypeAdapters {
}.nullSafe(); }.nullSafe();
public static final TypeAdapterFactory CURRENCY_FACTORY = newFactory(Currency.class, CURRENCY); public static final TypeAdapterFactory CURRENCY_FACTORY = newFactory(Currency.class, CURRENCY);
public static final TypeAdapter<Calendar> CALENDAR = new TypeAdapter<Calendar>() { public static final TypeAdapter<Calendar> CALENDAR =
new TypeAdapter<Calendar>() {
private static final String YEAR = "year"; private static final String YEAR = "year";
private static final String MONTH = "month"; private static final String MONTH = "month";
private static final String DAY_OF_MONTH = "dayOfMonth"; private static final String DAY_OF_MONTH = "dayOfMonth";
@ -693,7 +768,8 @@ public final class TypeAdapters {
public static final TypeAdapterFactory CALENDAR_FACTORY = public static final TypeAdapterFactory CALENDAR_FACTORY =
newFactoryForMultipleTypes(Calendar.class, GregorianCalendar.class, CALENDAR); newFactoryForMultipleTypes(Calendar.class, GregorianCalendar.class, CALENDAR);
public static final TypeAdapter<Locale> LOCALE = new TypeAdapter<Locale>() { public static final TypeAdapter<Locale> LOCALE =
new TypeAdapter<Locale>() {
@Override @Override
public Locale read(JsonReader in) throws IOException { public Locale read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
@ -722,6 +798,7 @@ public final class TypeAdapters {
return new Locale(language, country, variant); return new Locale(language, country, variant);
} }
} }
@Override @Override
public void write(JsonWriter out, Locale value) throws IOException { public void write(JsonWriter out, Locale value) throws IOException {
out.value(value == null ? null : value.toString()); out.value(value == null ? null : value.toString());
@ -730,10 +807,11 @@ public final class TypeAdapters {
public static final TypeAdapterFactory LOCALE_FACTORY = newFactory(Locale.class, LOCALE); public static final TypeAdapterFactory LOCALE_FACTORY = newFactory(Locale.class, LOCALE);
public static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() { public static final TypeAdapter<JsonElement> JSON_ELEMENT =
new TypeAdapter<JsonElement>() {
/** /**
* Tries to begin reading a JSON array or JSON object, returning {@code null} if * Tries to begin reading a JSON array or JSON object, returning {@code null} if the next
* the next element is neither of those. * element is neither of those.
*/ */
private JsonElement tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException { private JsonElement tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) { switch (peeked) {
@ -767,7 +845,8 @@ public final class TypeAdapters {
} }
} }
@Override public JsonElement read(JsonReader in) throws IOException { @Override
public JsonElement read(JsonReader in) throws IOException {
if (in instanceof JsonTreeReader) { if (in instanceof JsonTreeReader) {
return ((JsonTreeReader) in).nextJsonElement(); return ((JsonTreeReader) in).nextJsonElement();
} }
@ -827,7 +906,8 @@ public final class TypeAdapters {
} }
} }
@Override public void write(JsonWriter out, JsonElement value) throws IOException { @Override
public void write(JsonWriter out, JsonElement value) throws IOException {
if (value == null || value.isJsonNull()) { if (value == null || value.isJsonNull()) {
out.nullValue(); out.nullValue();
} else if (value.isJsonPrimitive()) { } else if (value.isJsonPrimitive()) {
@ -861,8 +941,8 @@ public final class TypeAdapters {
} }
}; };
public static final TypeAdapterFactory JSON_ELEMENT_FACTORY public static final TypeAdapterFactory JSON_ELEMENT_FACTORY =
= newTypeHierarchyFactory(JsonElement.class, JSON_ELEMENT); newTypeHierarchyFactory(JsonElement.class, JSON_ELEMENT);
private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> { private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
private final Map<String, T> nameToConstant = new HashMap<>(); private final Map<String, T> nameToConstant = new HashMap<>();
@ -871,11 +951,15 @@ public final class TypeAdapters {
public EnumTypeAdapter(final Class<T> classOfT) { public EnumTypeAdapter(final Class<T> classOfT) {
try { try {
// Uses reflection to find enum constants to work around name mismatches for obfuscated classes // Uses reflection to find enum constants to work around name mismatches for obfuscated
// Reflection access might throw SecurityException, therefore run this in privileged context; // classes Reflection access might throw SecurityException, therefore run this in privileged
// should be acceptable because this only retrieves enum constants, but does not expose anything else // context; should be acceptable because this only retrieves enum constants, but does not
Field[] constantFields = AccessController.doPrivileged(new PrivilegedAction<Field[]>() { // expose anything else
@Override public Field[] run() { Field[] constantFields =
AccessController.doPrivileged(
new PrivilegedAction<Field[]>() {
@Override
public Field[] run() {
Field[] fields = classOfT.getDeclaredFields(); Field[] fields = classOfT.getDeclaredFields();
ArrayList<Field> constantFieldsList = new ArrayList<>(fields.length); ArrayList<Field> constantFieldsList = new ArrayList<>(fields.length);
for (Field f : fields) { for (Field f : fields) {
@ -910,7 +994,9 @@ public final class TypeAdapters {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
@Override public T read(JsonReader in) throws IOException {
@Override
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -920,13 +1006,16 @@ public final class TypeAdapters {
return (constant == null) ? stringToConstant.get(key) : constant; return (constant == null) ? stringToConstant.get(key) : constant;
} }
@Override public void write(JsonWriter out, T value) throws IOException { @Override
public void write(JsonWriter out, T value) throws IOException {
out.value(value == null ? null : constantToName.get(value)); out.value(value == null ? null : constantToName.get(value));
} }
} }
public static final TypeAdapterFactory ENUM_FACTORY = new TypeAdapterFactory() { public static final TypeAdapterFactory ENUM_FACTORY =
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType(); Class<? super T> rawType = typeToken.getRawType();
if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) { if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
return null; return null;
@ -944,7 +1033,8 @@ public final class TypeAdapters {
final TypeToken<TT> type, final TypeAdapter<TT> typeAdapter) { final TypeToken<TT> type, final TypeAdapter<TT> typeAdapter) {
return new TypeAdapterFactory() { return new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
return typeToken.equals(type) ? (TypeAdapter<T>) typeAdapter : null; return typeToken.equals(type) ? (TypeAdapter<T>) typeAdapter : null;
} }
}; };
@ -954,10 +1044,13 @@ public final class TypeAdapters {
final Class<TT> type, final TypeAdapter<TT> typeAdapter) { final Class<TT> type, final TypeAdapter<TT> typeAdapter) {
return new TypeAdapterFactory() { return new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null; return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null;
} }
@Override public String toString() {
@Override
public String toString() {
return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]"; return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]";
} }
}; };
@ -967,28 +1060,46 @@ public final class TypeAdapters {
final Class<TT> unboxed, final Class<TT> boxed, final TypeAdapter<? super TT> typeAdapter) { final Class<TT> unboxed, final Class<TT> boxed, final TypeAdapter<? super TT> typeAdapter) {
return new TypeAdapterFactory() { return new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType(); Class<? super T> rawType = typeToken.getRawType();
return (rawType == unboxed || rawType == boxed) ? (TypeAdapter<T>) typeAdapter : null; return (rawType == unboxed || rawType == boxed) ? (TypeAdapter<T>) typeAdapter : null;
} }
@Override public String toString() {
return "Factory[type=" + boxed.getName() @Override
+ "+" + unboxed.getName() + ",adapter=" + typeAdapter + "]"; public String toString() {
return "Factory[type="
+ boxed.getName()
+ "+"
+ unboxed.getName()
+ ",adapter="
+ typeAdapter
+ "]";
} }
}; };
} }
public static <TT> TypeAdapterFactory newFactoryForMultipleTypes(final Class<TT> base, public static <TT> TypeAdapterFactory newFactoryForMultipleTypes(
final Class<? extends TT> sub, final TypeAdapter<? super TT> typeAdapter) { final Class<TT> base,
final Class<? extends TT> sub,
final TypeAdapter<? super TT> typeAdapter) {
return new TypeAdapterFactory() { return new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType(); Class<? super T> rawType = typeToken.getRawType();
return (rawType == base || rawType == sub) ? (TypeAdapter<T>) typeAdapter : null; return (rawType == base || rawType == sub) ? (TypeAdapter<T>) typeAdapter : null;
} }
@Override public String toString() {
return "Factory[type=" + base.getName() @Override
+ "+" + sub.getName() + ",adapter=" + typeAdapter + "]"; public String toString() {
return "Factory[type="
+ base.getName()
+ "+"
+ sub.getName()
+ ",adapter="
+ typeAdapter
+ "]";
} }
}; };
} }
@ -1001,27 +1112,38 @@ public final class TypeAdapters {
final Class<T1> clazz, final TypeAdapter<T1> typeAdapter) { final Class<T1> clazz, final TypeAdapter<T1> typeAdapter) {
return new TypeAdapterFactory() { return new TypeAdapterFactory() {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override public <T2> TypeAdapter<T2> create(Gson gson, TypeToken<T2> typeToken) { @Override
public <T2> TypeAdapter<T2> create(Gson gson, TypeToken<T2> typeToken) {
final Class<? super T2> requestedType = typeToken.getRawType(); final Class<? super T2> requestedType = typeToken.getRawType();
if (!clazz.isAssignableFrom(requestedType)) { if (!clazz.isAssignableFrom(requestedType)) {
return null; return null;
} }
return (TypeAdapter<T2>) new TypeAdapter<T1>() { return (TypeAdapter<T2>)
@Override public void write(JsonWriter out, T1 value) throws IOException { new TypeAdapter<T1>() {
@Override
public void write(JsonWriter out, T1 value) throws IOException {
typeAdapter.write(out, value); typeAdapter.write(out, value);
} }
@Override public T1 read(JsonReader in) throws IOException { @Override
public T1 read(JsonReader in) throws IOException {
T1 result = typeAdapter.read(in); T1 result = typeAdapter.read(in);
if (result != null && !requestedType.isInstance(result)) { if (result != null && !requestedType.isInstance(result)) {
throw new JsonSyntaxException("Expected a " + requestedType.getName() throw new JsonSyntaxException(
+ " but was " + result.getClass().getName() + "; at path " + in.getPreviousPath()); "Expected a "
+ requestedType.getName()
+ " but was "
+ result.getClass().getName()
+ "; at path "
+ in.getPreviousPath());
} }
return result; return result;
} }
}; };
} }
@Override public String toString() {
@Override
public String toString() {
return "Factory[typeHierarchy=" + clazz.getName() + ",adapter=" + typeAdapter + "]"; return "Factory[typeHierarchy=" + clazz.getName() + ",adapter=" + typeAdapter + "]";
} }
}; };

View File

@ -25,23 +25,24 @@ import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
/** /**
* Utilities methods for manipulating dates in iso8601 format. This is much faster and GC friendly than using SimpleDateFormat so * Utilities methods for manipulating dates in iso8601 format. This is much faster and GC friendly
* highly suitable if you (un)serialize lots of date objects. * than using SimpleDateFormat so highly suitable if you (un)serialize lots of date objects.
* *
* Supported parse format: [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh[:]mm]] * <p>Supported parse format:
* [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh[:]mm]]
* *
* @see <a href="http://www.w3.org/TR/NOTE-datetime">this specification</a> * @see <a href="http://www.w3.org/TR/NOTE-datetime">this specification</a>
*/ */
// Date parsing code from Jackson databind ISO8601Utils.java // Date parsing code from Jackson databind ISO8601Utils.java
// https://github.com/FasterXML/jackson-databind/blob/2.8/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java // https://github.com/FasterXML/jackson-databind/blob/2.8/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
public class ISO8601Utils public class ISO8601Utils {
{
/** /**
* ID to represent the 'UTC' string, default timezone since Jackson 2.7 * ID to represent the 'UTC' string, default timezone since Jackson 2.7
* *
* @since 2.7 * @since 2.7
*/ */
private static final String UTC_ID = "UTC"; private static final String UTC_ID = "UTC";
/** /**
* The UTC timezone, prefetched to avoid more lookups. * The UTC timezone, prefetched to avoid more lookups.
* *
@ -163,7 +164,8 @@ public class ISO8601Utils
int hour = 0; int hour = 0;
int minutes = 0; int minutes = 0;
int seconds = 0; int seconds = 0;
int milliseconds = 0; // always use 0 otherwise returned date will include millis of current time int milliseconds =
0; // always use 0 otherwise returned date will include millis of current time
// if the value has no time component (and no time zone), we are done // if the value has no time component (and no time zone), we are done
boolean hasT = checkOffset(date, offset, 'T'); boolean hasT = checkOffset(date, offset, 'T');
@ -231,7 +233,8 @@ public class ISO8601Utils
} else if (timezoneIndicator == '+' || timezoneIndicator == '-') { } else if (timezoneIndicator == '+' || timezoneIndicator == '-') {
String timezoneOffset = date.substring(offset); String timezoneOffset = date.substring(offset);
// When timezone has no minutes, we should append it, valid timezones are, for example: +00:00, +0000 and +00 // When timezone has no minutes, we should append it, valid timezones are, for example:
// +00:00, +0000 and +00
timezoneOffset = timezoneOffset.length() >= 5 ? timezoneOffset : timezoneOffset + "00"; timezoneOffset = timezoneOffset.length() >= 5 ? timezoneOffset : timezoneOffset + "00";
offset += timezoneOffset.length(); offset += timezoneOffset.length();
@ -244,7 +247,7 @@ public class ISO8601Utils
// `java.util.TimeZone` specifically instruct use of GMT as base for // `java.util.TimeZone` specifically instruct use of GMT as base for
// custom timezones... odd. // custom timezones... odd.
String timezoneId = "GMT" + timezoneOffset; String timezoneId = "GMT" + timezoneOffset;
// String timezoneId = "UTC" + timezoneOffset; // String timezoneId = "UTC" + timezoneOffset;
timezone = TimeZone.getTimeZone(timezoneId); timezone = TimeZone.getTimeZone(timezoneId);
@ -257,13 +260,17 @@ public class ISO8601Utils
*/ */
String cleaned = act.replace(":", ""); String cleaned = act.replace(":", "");
if (!cleaned.equals(timezoneId)) { if (!cleaned.equals(timezoneId)) {
throw new IndexOutOfBoundsException("Mismatching time zone indicator: "+timezoneId+" given, resolves to " throw new IndexOutOfBoundsException(
+timezone.getID()); "Mismatching time zone indicator: "
+ timezoneId
+ " given, resolves to "
+ timezone.getID());
} }
} }
} }
} else { } else {
throw new IndexOutOfBoundsException("Invalid time zone indicator '" + timezoneIndicator+"'"); throw new IndexOutOfBoundsException(
"Invalid time zone indicator '" + timezoneIndicator + "'");
} }
Calendar calendar = new GregorianCalendar(timezone); Calendar calendar = new GregorianCalendar(timezone);
@ -290,9 +297,10 @@ public class ISO8601Utils
String input = (date == null) ? null : ('"' + date + '"'); String input = (date == null) ? null : ('"' + date + '"');
String msg = fail.getMessage(); String msg = fail.getMessage();
if (msg == null || msg.isEmpty()) { if (msg == null || msg.isEmpty()) {
msg = "("+fail.getClass().getName()+")"; msg = "(" + fail.getClass().getName() + ")";
} }
ParseException ex = new ParseException("Failed to parse date [" + input + "]: " + msg, pos.getIndex()); ParseException ex =
new ParseException("Failed to parse date [" + input + "]: " + msg, pos.getIndex());
ex.initCause(fail); ex.initCause(fail);
throw ex; throw ex;
} }
@ -318,7 +326,8 @@ public class ISO8601Utils
* @return the int * @return the int
* @throws NumberFormatException if the value is not a number * @throws NumberFormatException if the value is not a number
*/ */
private static int parseInt(String value, int beginIndex, int endIndex) throws NumberFormatException { private static int parseInt(String value, int beginIndex, int endIndex)
throws NumberFormatException {
if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) { if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) {
throw new NumberFormatException(value); throw new NumberFormatException(value);
} }
@ -369,5 +378,4 @@ public class ISO8601Utils
} }
return string.length(); return string.length();
} }
} }

View File

@ -15,8 +15,8 @@
*/ */
/** /**
* Do NOT use any class in this package as they are meant for internal use in Gson. * Do NOT use any class in this package as they are meant for internal use in Gson. These classes
* These classes will very likely change incompatibly in future versions. You have been warned. * will very likely change incompatibly in future versions. You have been warned.
* *
* @author Inderjeet Singh, Joel Leitch, Jesse Wilson * @author Inderjeet Singh, Joel Leitch, Jesse Wilson
*/ */

View File

@ -31,7 +31,8 @@ public class ReflectionHelper {
static { static {
RecordHelper instance; RecordHelper instance;
try { try {
// Try to construct the RecordSupportedHelper, if this fails, records are not supported on this JVM. // Try to construct the RecordSupportedHelper, if this fails, records are not supported on
// this JVM.
instance = new RecordSupportedHelper(); instance = new RecordSupportedHelper();
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
instance = new RecordNotSupportedHelper(); instance = new RecordNotSupportedHelper();
@ -45,8 +46,10 @@ public class ReflectionHelper {
// Class was added in Java 9, therefore cannot use instanceof // Class was added in Java 9, therefore cannot use instanceof
if (e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) { if (e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
String message = e.getMessage(); String message = e.getMessage();
String troubleshootingId = message != null && message.contains("to module com.google.gson") String troubleshootingId =
? "reflection-inaccessible-to-module-gson" : "reflection-inaccessible"; message != null && message.contains("to module com.google.gson")
? "reflection-inaccessible-to-module-gson"
: "reflection-inaccessible";
return "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId); return "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId);
} }
return ""; return "";
@ -55,7 +58,8 @@ public class ReflectionHelper {
/** /**
* Internal implementation of making an {@link AccessibleObject} accessible. * Internal implementation of making an {@link AccessibleObject} accessible.
* *
* @param object the object that {@link AccessibleObject#setAccessible(boolean)} should be called on. * @param object the object that {@link AccessibleObject#setAccessible(boolean)} should be called
* on.
* @throws JsonIOException if making the object accessible fails * @throws JsonIOException if making the object accessible fails
*/ */
public static void makeAccessible(AccessibleObject object) throws JsonIOException { public static void makeAccessible(AccessibleObject object) throws JsonIOException {
@ -63,22 +67,26 @@ public class ReflectionHelper {
object.setAccessible(true); object.setAccessible(true);
} catch (Exception exception) { } catch (Exception exception) {
String description = getAccessibleObjectDescription(object, false); String description = getAccessibleObjectDescription(object, false);
throw new JsonIOException("Failed making " + description + " accessible; either increase its visibility" throw new JsonIOException(
+ " or write a custom TypeAdapter for its declaring type." + getInaccessibleTroubleshootingSuffix(exception), "Failed making "
+ description
+ " accessible; either increase its visibility"
+ " or write a custom TypeAdapter for its declaring type."
+ getInaccessibleTroubleshootingSuffix(exception),
exception); exception);
} }
} }
/** /**
* Returns a short string describing the {@link AccessibleObject} in a human-readable way. * Returns a short string describing the {@link AccessibleObject} in a human-readable way. The
* The result is normally shorter than {@link AccessibleObject#toString()} because it omits * result is normally shorter than {@link AccessibleObject#toString()} because it omits modifiers
* modifiers (e.g. {@code final}) and uses simple names for constructor and method parameter * (e.g. {@code final}) and uses simple names for constructor and method parameter types.
* types.
* *
* @param object object to describe * @param object object to describe
* @param uppercaseFirstLetter whether the first letter of the description should be uppercased * @param uppercaseFirstLetter whether the first letter of the description should be uppercased
*/ */
public static String getAccessibleObjectDescription(AccessibleObject object, boolean uppercaseFirstLetter) { public static String getAccessibleObjectDescription(
AccessibleObject object, boolean uppercaseFirstLetter) {
String description; String description;
if (object instanceof Field) { if (object instanceof Field) {
@ -103,17 +111,14 @@ public class ReflectionHelper {
return description; return description;
} }
/** /** Creates a string representation for a field, omitting modifiers and the field type. */
* Creates a string representation for a field, omitting modifiers and
* the field type.
*/
public static String fieldToString(Field field) { public static String fieldToString(Field field) {
return field.getDeclaringClass().getName() + "#" + field.getName(); return field.getDeclaringClass().getName() + "#" + field.getName();
} }
/** /**
* Creates a string representation for a constructor. * Creates a string representation for a constructor. E.g.: {@code java.lang.String(char[], int,
* E.g.: {@code java.lang.String(char[], int, int)} * int)}
*/ */
public static String constructorToString(Constructor<?> constructor) { public static String constructorToString(Constructor<?> constructor) {
StringBuilder stringBuilder = new StringBuilder(constructor.getDeclaringClass().getName()); StringBuilder stringBuilder = new StringBuilder(constructor.getDeclaringClass().getName());
@ -122,11 +127,13 @@ public class ReflectionHelper {
return stringBuilder.toString(); return stringBuilder.toString();
} }
// Note: Ideally parameter type would be java.lang.reflect.Executable, but that was added in Java 8 // Ideally parameter type would be java.lang.reflect.Executable, but that was added in Java 8
private static void appendExecutableParameters(AccessibleObject executable, StringBuilder stringBuilder) { private static void appendExecutableParameters(
AccessibleObject executable, StringBuilder stringBuilder) {
stringBuilder.append('('); stringBuilder.append('(');
Class<?>[] parameters = (executable instanceof Method) Class<?>[] parameters =
(executable instanceof Method)
? ((Method) executable).getParameterTypes() ? ((Method) executable).getParameterTypes()
: ((Constructor<?>) executable).getParameterTypes(); : ((Constructor<?>) executable).getParameterTypes();
for (int i = 0; i < parameters.length; i++) { for (int i = 0; i < parameters.length; i++) {
@ -140,22 +147,24 @@ public class ReflectionHelper {
} }
/** /**
* Tries making the constructor accessible, returning an exception message * Tries making the constructor accessible, returning an exception message if this fails.
* if this fails.
* *
* @param constructor constructor to make accessible * @param constructor constructor to make accessible
* @return exception message; {@code null} if successful, non-{@code null} if * @return exception message; {@code null} if successful, non-{@code null} if unsuccessful
* unsuccessful
*/ */
public static String tryMakeAccessible(Constructor<?> constructor) { public static String tryMakeAccessible(Constructor<?> constructor) {
try { try {
constructor.setAccessible(true); constructor.setAccessible(true);
return null; return null;
} catch (Exception exception) { } catch (Exception exception) {
return "Failed making constructor '" + constructorToString(constructor) + "' accessible;" return "Failed making constructor '"
+ constructorToString(constructor)
+ "' accessible;"
+ " either increase its visibility or write a custom InstanceCreator or TypeAdapter for" + " either increase its visibility or write a custom InstanceCreator or TypeAdapter for"
// Include the message since it might contain more detailed information // Include the message since it might contain more detailed information
+ " its declaring type: " + exception.getMessage() + getInaccessibleTroubleshootingSuffix(exception); + " its declaring type: "
+ exception.getMessage()
+ getInaccessibleTroubleshootingSuffix(exception);
} }
} }
@ -179,26 +188,28 @@ public class ReflectionHelper {
public static RuntimeException createExceptionForUnexpectedIllegalAccess( public static RuntimeException createExceptionForUnexpectedIllegalAccess(
IllegalAccessException exception) { IllegalAccessException exception) {
throw new RuntimeException("Unexpected IllegalAccessException occurred (Gson " + GsonBuildConfig.VERSION + ")." throw new RuntimeException(
+ " Certain ReflectionAccessFilter features require Java >= 9 to work correctly. If you are not using" "Unexpected IllegalAccessException occurred (Gson "
+ " ReflectionAccessFilter, report this to the Gson maintainers.", + GsonBuildConfig.VERSION
+ "). Certain ReflectionAccessFilter features require Java >= 9 to work correctly. If"
+ " you are not using ReflectionAccessFilter, report this to the Gson maintainers.",
exception); exception);
} }
private static RuntimeException createExceptionForRecordReflectionException( private static RuntimeException createExceptionForRecordReflectionException(
ReflectiveOperationException exception) { ReflectiveOperationException exception) {
throw new RuntimeException("Unexpected ReflectiveOperationException occurred" throw new RuntimeException(
+ " (Gson " + GsonBuildConfig.VERSION + ")." "Unexpected ReflectiveOperationException occurred"
+ " (Gson "
+ GsonBuildConfig.VERSION
+ ")."
+ " To support Java records, reflection is utilized to read out information" + " To support Java records, reflection is utilized to read out information"
+ " about records. All these invocations happens after it is established" + " about records. All these invocations happens after it is established"
+ " that records exist in the JVM. This exception is unexpected behavior.", + " that records exist in the JVM. This exception is unexpected behavior.",
exception); exception);
} }
/** /** Internal abstraction over reflection when Records are supported. */
* Internal abstraction over reflection when Records are supported.
*/
private abstract static class RecordHelper { private abstract static class RecordHelper {
abstract boolean isRecord(Class<?> clazz); abstract boolean isRecord(Class<?> clazz);
@ -254,8 +265,8 @@ public class ReflectionHelper {
for (int i = 0; i < recordComponents.length; i++) { for (int i = 0; i < recordComponents.length; i++) {
recordComponentTypes[i] = (Class<?>) getType.invoke(recordComponents[i]); recordComponentTypes[i] = (Class<?>) getType.invoke(recordComponents[i]);
} }
// Uses getDeclaredConstructor because implicit constructor has same visibility as record and might // Uses getDeclaredConstructor because implicit constructor has same visibility as record
// therefore not be public // and might therefore not be public
return raw.getDeclaredConstructor(recordComponentTypes); return raw.getDeclaredConstructor(recordComponentTypes);
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw createExceptionForRecordReflectionException(e); throw createExceptionForRecordReflectionException(e);
@ -265,8 +276,9 @@ public class ReflectionHelper {
@Override @Override
public Method getAccessor(Class<?> raw, Field field) { public Method getAccessor(Class<?> raw, Field field) {
try { try {
// Records consists of record components, each with a unique name, a corresponding field and accessor method // Records consists of record components, each with a unique name, a corresponding field and
// with the same name. Ref.: https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.10.3 // accessor method with the same name. Ref.:
// https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.10.3
return raw.getMethod(field.getName()); return raw.getMethod(field.getName());
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
throw createExceptionForRecordReflectionException(e); throw createExceptionForRecordReflectionException(e);
@ -274,9 +286,7 @@ public class ReflectionHelper {
} }
} }
/** /** Instance used when records are not supported */
* Instance used when records are not supported
*/
private static class RecordNotSupportedHelper extends RecordHelper { private static class RecordNotSupportedHelper extends RecordHelper {
@Override @Override

View File

@ -31,25 +31,26 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
/** /**
* Adapter for java.sql.Date. Although this class appears stateless, it is not. * Adapter for java.sql.Date. Although this class appears stateless, it is not. DateFormat captures
* DateFormat captures its time zone and locale when it is created, which gives * its time zone and locale when it is created, which gives this class state. DateFormat isn't
* this class state. DateFormat isn't thread safe either, so this class has * thread safe either, so this class has to synchronize its read and write methods.
* to synchronize its read and write methods.
*/ */
@SuppressWarnings("JavaUtilDate") @SuppressWarnings("JavaUtilDate")
final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> { final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { static final TypeAdapterFactory FACTORY =
new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
return typeToken.getRawType() == java.sql.Date.class return typeToken.getRawType() == java.sql.Date.class
? (TypeAdapter<T>) new SqlDateTypeAdapter() : null; ? (TypeAdapter<T>) new SqlDateTypeAdapter()
: null;
} }
}; };
private final DateFormat format = new SimpleDateFormat("MMM d, yyyy"); private final DateFormat format = new SimpleDateFormat("MMM d, yyyy");
private SqlDateTypeAdapter() { private SqlDateTypeAdapter() {}
}
@Override @Override
public java.sql.Date read(JsonReader in) throws IOException { public java.sql.Date read(JsonReader in) throws IOException {
@ -65,7 +66,8 @@ final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
} }
return new java.sql.Date(utilDate.getTime()); return new java.sql.Date(utilDate.getTime());
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as SQL Date; at path " + in.getPreviousPath(), e); throw new JsonSyntaxException(
"Failed parsing '" + s + "' as SQL Date; at path " + in.getPreviousPath(), e);
} }
} }

View File

@ -32,26 +32,29 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
/** /**
* Adapter for java.sql.Time. Although this class appears stateless, it is not. * Adapter for java.sql.Time. Although this class appears stateless, it is not. DateFormat captures
* DateFormat captures its time zone and locale when it is created, which gives * its time zone and locale when it is created, which gives this class state. DateFormat isn't
* this class state. DateFormat isn't thread safe either, so this class has * thread safe either, so this class has to synchronize its read and write methods.
* to synchronize its read and write methods.
*/ */
@SuppressWarnings("JavaUtilDate") @SuppressWarnings("JavaUtilDate")
final class SqlTimeTypeAdapter extends TypeAdapter<Time> { final class SqlTimeTypeAdapter extends TypeAdapter<Time> {
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { static final TypeAdapterFactory FACTORY =
new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
return typeToken.getRawType() == Time.class ? (TypeAdapter<T>) new SqlTimeTypeAdapter() : null; public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
return typeToken.getRawType() == Time.class
? (TypeAdapter<T>) new SqlTimeTypeAdapter()
: null;
} }
}; };
private final DateFormat format = new SimpleDateFormat("hh:mm:ss a"); private final DateFormat format = new SimpleDateFormat("hh:mm:ss a");
private SqlTimeTypeAdapter() { private SqlTimeTypeAdapter() {}
}
@Override public Time read(JsonReader in) throws IOException { @Override
public Time read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
@ -63,11 +66,13 @@ final class SqlTimeTypeAdapter extends TypeAdapter<Time> {
return new Time(date.getTime()); return new Time(date.getTime());
} }
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as SQL Time; at path " + in.getPreviousPath(), e); throw new JsonSyntaxException(
"Failed parsing '" + s + "' as SQL Time; at path " + in.getPreviousPath(), e);
} }
} }
@Override public void write(JsonWriter out, Time value) throws IOException { @Override
public void write(JsonWriter out, Time value) throws IOException {
if (value == null) { if (value == null) {
out.nullValue(); out.nullValue();
return; return;

View File

@ -22,16 +22,17 @@ import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.Date; import java.util.Date;
@SuppressWarnings("JavaUtilDate") @SuppressWarnings("JavaUtilDate")
class SqlTimestampTypeAdapter extends TypeAdapter<Timestamp> { class SqlTimestampTypeAdapter extends TypeAdapter<Timestamp> {
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { static final TypeAdapterFactory FACTORY =
new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { @Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (typeToken.getRawType() == Timestamp.class) { if (typeToken.getRawType() == Timestamp.class) {
final TypeAdapter<Date> dateTypeAdapter = gson.getAdapter(Date.class); final TypeAdapter<Date> dateTypeAdapter = gson.getAdapter(Date.class);
return (TypeAdapter<T>) new SqlTimestampTypeAdapter(dateTypeAdapter); return (TypeAdapter<T>) new SqlTimestampTypeAdapter(dateTypeAdapter);

View File

@ -16,29 +16,23 @@
package com.google.gson.internal.sql; package com.google.gson.internal.sql;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.Date; import java.util.Date;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType;
/** /**
* Encapsulates access to {@code java.sql} types, to allow Gson to * Encapsulates access to {@code java.sql} types, to allow Gson to work without the {@code java.sql}
* work without the {@code java.sql} module being present. * module being present. No {@link ClassNotFoundException}s will be thrown in case the {@code
* No {@link ClassNotFoundException}s will be thrown in case * java.sql} module is not present.
* the {@code java.sql} module is not present.
* *
* <p>If {@link #SUPPORTS_SQL_TYPES} is {@code true}, all other * <p>If {@link #SUPPORTS_SQL_TYPES} is {@code true}, all other constants of this class will be
* constants of this class will be non-{@code null}. However, if * non-{@code null}. However, if it is {@code false} all other constants will be {@code null} and
* it is {@code false} all other constants will be {@code null} and
* there will be no support for {@code java.sql} types. * there will be no support for {@code java.sql} types.
*/ */
@SuppressWarnings("JavaUtilDate") @SuppressWarnings("JavaUtilDate")
public final class SqlTypesSupport { public final class SqlTypesSupport {
/** /** {@code true} if {@code java.sql} types are supported, {@code false} otherwise */
* {@code true} if {@code java.sql} types are supported,
* {@code false} otherwise
*/
public static final boolean SUPPORTS_SQL_TYPES; public static final boolean SUPPORTS_SQL_TYPES;
public static final DateType<? extends Date> DATE_DATE_TYPE; public static final DateType<? extends Date> DATE_DATE_TYPE;
@ -59,13 +53,17 @@ public final class SqlTypesSupport {
SUPPORTS_SQL_TYPES = sqlTypesSupport; SUPPORTS_SQL_TYPES = sqlTypesSupport;
if (SUPPORTS_SQL_TYPES) { if (SUPPORTS_SQL_TYPES) {
DATE_DATE_TYPE = new DateType<java.sql.Date>(java.sql.Date.class) { DATE_DATE_TYPE =
@Override protected java.sql.Date deserialize(Date date) { new DateType<java.sql.Date>(java.sql.Date.class) {
@Override
protected java.sql.Date deserialize(Date date) {
return new java.sql.Date(date.getTime()); return new java.sql.Date(date.getTime());
} }
}; };
TIMESTAMP_DATE_TYPE = new DateType<Timestamp>(Timestamp.class) { TIMESTAMP_DATE_TYPE =
@Override protected Timestamp deserialize(Date date) { new DateType<Timestamp>(Timestamp.class) {
@Override
protected Timestamp deserialize(Date date) {
return new Timestamp(date.getTime()); return new Timestamp(date.getTime());
} }
}; };
@ -83,6 +81,5 @@ public final class SqlTypesSupport {
} }
} }
private SqlTypesSupport() { private SqlTypesSupport() {}
}
} }

View File

@ -18,9 +18,9 @@
* This package provides the {@link com.google.gson.Gson} class to convert Json to Java and * This package provides the {@link com.google.gson.Gson} class to convert Json to Java and
* vice-versa. * vice-versa.
* *
* <p>The primary class to use is {@link com.google.gson.Gson} which can be constructed with * <p>The primary class to use is {@link com.google.gson.Gson} which can be constructed with {@code
* {@code new Gson()} (using default settings) or by using {@link com.google.gson.GsonBuilder} * new Gson()} (using default settings) or by using {@link com.google.gson.GsonBuilder} (to
* (to configure various options such as using versioning and so on).</p> * configure various options such as using versioning and so on).
* *
* @author Inderjeet Singh, Joel Leitch * @author Inderjeet Singh, Joel Leitch
*/ */

View File

@ -28,28 +28,24 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
/** /**
* Represents a generic type {@code T}. Java doesn't yet provide a way to * Represents a generic type {@code T}. Java doesn't yet provide a way to represent generic types,
* represent generic types, so this class does. Forces clients to create a * so this class does. Forces clients to create a subclass of this class which enables retrieval the
* subclass of this class which enables retrieval the type information even at * type information even at runtime.
* runtime.
* *
* <p>For example, to create a type literal for {@code List<String>}, you can * <p>For example, to create a type literal for {@code List<String>}, you can create an empty
* create an empty anonymous class: * anonymous class:
* *
* <p> * <p>{@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
* {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
* *
* <p>Capturing a type variable as type argument of an anonymous {@code TypeToken} * <p>Capturing a type variable as type argument of an anonymous {@code TypeToken} subclass is not
* subclass is not allowed, for example {@code TypeToken<List<T>>}. * allowed, for example {@code TypeToken<List<T>>}. Due to type erasure the runtime type of a type
* Due to type erasure the runtime type of a type variable is not available * variable is not available to Gson and therefore it cannot provide the functionality one might
* to Gson and therefore it cannot provide the functionality one might expect. * expect. This would give a false sense of type-safety at compile time and could lead to an
* This would give a false sense of type-safety at compile time and could * unexpected {@code ClassCastException} at runtime.
* lead to an unexpected {@code ClassCastException} at runtime.
* *
* <p>If the type arguments of the parameterized type are only available at * <p>If the type arguments of the parameterized type are only available at runtime, for example
* runtime, for example when you want to create a {@code List<E>} based on * when you want to create a {@code List<E>} based on a {@code Class<E>} representing the element
* a {@code Class<E>} representing the element type, the method * type, the method {@link #getParameterized(Type, Type...)} can be used.
* {@link #getParameterized(Type, Type...)} can be used.
* *
* @author Bob Lee * @author Bob Lee
* @author Sven Mawson * @author Sven Mawson
@ -61,19 +57,17 @@ public class TypeToken<T> {
private final int hashCode; private final int hashCode;
/** /**
* Constructs a new type literal. Derives represented class from type * Constructs a new type literal. Derives represented class from type parameter.
* parameter.
* *
* <p>Clients create an empty anonymous subclass. Doing so embeds the type * <p>Clients create an empty anonymous subclass. Doing so embeds the type parameter in the
* parameter in the anonymous class's type hierarchy so we can reconstitute it * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure, for
* at runtime despite erasure, for example: * example:
* <p>
* {@code new TypeToken<List<String>>() {}}
* *
* @throws IllegalArgumentException * <p>{@code new TypeToken<List<String>>() {}}
* If the anonymous {@code TypeToken} subclass captures a type variable, *
* for example {@code TypeToken<List<T>>}. See the {@code TypeToken} * @throws IllegalArgumentException If the anonymous {@code TypeToken} subclass captures a type
* class documentation for more details. * variable, for example {@code TypeToken<List<T>>}. See the {@code TypeToken} class
* documentation for more details.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected TypeToken() { protected TypeToken() {
@ -82,9 +76,7 @@ public class TypeToken<T> {
this.hashCode = type.hashCode(); this.hashCode = type.hashCode();
} }
/** /** Unsafe. Constructs a type literal manually. */
* Unsafe. Constructs a type literal manually.
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private TypeToken(Type type) { private TypeToken(Type type) {
this.type = $Gson$Types.canonicalize(Objects.requireNonNull(type)); this.type = $Gson$Types.canonicalize(Objects.requireNonNull(type));
@ -97,9 +89,8 @@ public class TypeToken<T> {
} }
/** /**
* Verifies that {@code this} is an instance of a direct subclass of TypeToken and * Verifies that {@code this} is an instance of a direct subclass of TypeToken and returns the
* returns the type argument for {@code T} in {@link $Gson$Types#canonicalize * type argument for {@code T} in {@link $Gson$Types#canonicalize canonical form}.
* canonical form}.
*/ */
private Type getTypeTokenTypeArgument() { private Type getTypeTokenTypeArgument() {
Type superclass = getClass().getGenericSuperclass(); Type superclass = getClass().getGenericSuperclass();
@ -116,10 +107,11 @@ public class TypeToken<T> {
} }
// Check for raw TypeToken as superclass // Check for raw TypeToken as superclass
else if (superclass == TypeToken.class) { else if (superclass == TypeToken.class) {
throw new IllegalStateException("TypeToken must be created with a type argument: new TypeToken<...>() {};" throw new IllegalStateException(
+ " When using code shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved." "TypeToken must be created with a type argument: new TypeToken<...>() {}; When using code"
+ "\nSee " + TroubleshootingGuide.createUrl("type-token-raw") + " shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved.\n"
); + "See "
+ TroubleshootingGuide.createUrl("type-token-raw"));
} }
// User created subclass of subclass of TypeToken // User created subclass of subclass of TypeToken
@ -129,9 +121,13 @@ public class TypeToken<T> {
private static void verifyNoTypeVariable(Type type) { private static void verifyNoTypeVariable(Type type) {
if (type instanceof TypeVariable) { if (type instanceof TypeVariable) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type; TypeVariable<?> typeVariable = (TypeVariable<?>) type;
throw new IllegalArgumentException("TypeToken type argument must not contain a type variable; captured type variable " throw new IllegalArgumentException(
+ typeVariable.getName() + " declared by " + typeVariable.getGenericDeclaration() "TypeToken type argument must not contain a type variable; captured type variable "
+ "\nSee " + TroubleshootingGuide.createUrl("typetoken-type-variable")); + typeVariable.getName()
+ " declared by "
+ typeVariable.getGenericDeclaration()
+ "\nSee "
+ TroubleshootingGuide.createUrl("typetoken-type-variable"));
} else if (type instanceof GenericArrayType) { } else if (type instanceof GenericArrayType) {
verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType()); verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType());
} else if (type instanceof ParameterizedType) { } else if (type instanceof ParameterizedType) {
@ -153,22 +149,20 @@ public class TypeToken<T> {
verifyNoTypeVariable(bound); verifyNoTypeVariable(bound);
} }
} else if (type == null) { } else if (type == null) {
// Occurs in Eclipse IDE and certain Java versions (e.g. Java 11.0.18) when capturing type variable // Occurs in Eclipse IDE and certain Java versions (e.g. Java 11.0.18) when capturing type
// declared by method of local class, see https://github.com/eclipse-jdt/eclipse.jdt.core/issues/975 // variable declared by method of local class, see
throw new IllegalArgumentException("TypeToken captured `null` as type argument; probably a compiler / runtime bug"); // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/975
throw new IllegalArgumentException(
"TypeToken captured `null` as type argument; probably a compiler / runtime bug");
} }
} }
/** /** Returns the raw (non-generic) type for this type. */
* Returns the raw (non-generic) type for this type.
*/
public final Class<? super T> getRawType() { public final Class<? super T> getRawType() {
return rawType; return rawType;
} }
/** /** Gets underlying {@code Type} instance. */
* Gets underlying {@code Type} instance.
*/
public final Type getType() { public final Type getType() {
return type; return type;
} }
@ -176,8 +170,7 @@ public class TypeToken<T> {
/** /**
* Check if this type is assignable from the given class object. * Check if this type is assignable from the given class object.
* *
* @deprecated this implementation may be inconsistent with javac for types * @deprecated this implementation may be inconsistent with javac for types with wildcards.
* with wildcards.
*/ */
@Deprecated @Deprecated
public boolean isAssignableFrom(Class<?> cls) { public boolean isAssignableFrom(Class<?> cls) {
@ -187,8 +180,7 @@ public class TypeToken<T> {
/** /**
* Check if this type is assignable from the given Type. * Check if this type is assignable from the given Type.
* *
* @deprecated this implementation may be inconsistent with javac for types * @deprecated this implementation may be inconsistent with javac for types with wildcards.
* with wildcards.
*/ */
@Deprecated @Deprecated
public boolean isAssignableFrom(Type from) { public boolean isAssignableFrom(Type from) {
@ -203,8 +195,7 @@ public class TypeToken<T> {
if (type instanceof Class<?>) { if (type instanceof Class<?>) {
return rawType.isAssignableFrom($Gson$Types.getRawType(from)); return rawType.isAssignableFrom($Gson$Types.getRawType(from));
} else if (type instanceof ParameterizedType) { } else if (type instanceof ParameterizedType) {
return isAssignableFrom(from, (ParameterizedType) type, return isAssignableFrom(from, (ParameterizedType) type, new HashMap<String, Type>());
new HashMap<String, Type>());
} else if (type instanceof GenericArrayType) { } else if (type instanceof GenericArrayType) {
return rawType.isAssignableFrom($Gson$Types.getRawType(from)) return rawType.isAssignableFrom($Gson$Types.getRawType(from))
&& isAssignableFrom(from, (GenericArrayType) type); && isAssignableFrom(from, (GenericArrayType) type);
@ -217,8 +208,7 @@ public class TypeToken<T> {
/** /**
* Check if this type is assignable from the given type token. * Check if this type is assignable from the given type token.
* *
* @deprecated this implementation may be inconsistent with javac for types * @deprecated this implementation may be inconsistent with javac for types with wildcards.
* with wildcards.
*/ */
@Deprecated @Deprecated
public boolean isAssignableFrom(TypeToken<?> token) { public boolean isAssignableFrom(TypeToken<?> token) {
@ -226,8 +216,8 @@ public class TypeToken<T> {
} }
/** /**
* Private helper function that performs some assignability checks for * Private helper function that performs some assignability checks for the provided
* the provided GenericArrayType. * GenericArrayType.
*/ */
private static boolean isAssignableFrom(Type from, GenericArrayType to) { private static boolean isAssignableFrom(Type from, GenericArrayType to) {
Type toGenericComponentType = to.getGenericComponentType(); Type toGenericComponentType = to.getGenericComponentType();
@ -242,20 +232,17 @@ public class TypeToken<T> {
} }
t = classType; t = classType;
} }
return isAssignableFrom(t, (ParameterizedType) toGenericComponentType, return isAssignableFrom(
new HashMap<String, Type>()); t, (ParameterizedType) toGenericComponentType, new HashMap<String, Type>());
} }
// No generic defined on "to"; therefore, return true and let other // No generic defined on "to"; therefore, return true and let other
// checks determine assignability // checks determine assignability
return true; return true;
} }
/** /** Private recursive helper function to actually do the type-safe checking of assignability. */
* Private recursive helper function to actually do the type-safe checking private static boolean isAssignableFrom(
* of assignability. Type from, ParameterizedType to, Map<String, Type> typeVarMap) {
*/
private static boolean isAssignableFrom(Type from, ParameterizedType to,
Map<String, Type> typeVarMap) {
if (from == null) { if (from == null) {
return false; return false;
@ -304,11 +291,11 @@ public class TypeToken<T> {
} }
/** /**
* Checks if two parameterized types are exactly equal, under the variable * Checks if two parameterized types are exactly equal, under the variable replacement described
* replacement described in the typeVarMap. * in the typeVarMap.
*/ */
private static boolean typeEquals(ParameterizedType from, private static boolean typeEquals(
ParameterizedType to, Map<String, Type> typeVarMap) { ParameterizedType from, ParameterizedType to, Map<String, Type> typeVarMap) {
if (from.getRawType().equals(to.getRawType())) { if (from.getRawType().equals(to.getRawType())) {
Type[] fromArgs = from.getActualTypeArguments(); Type[] fromArgs = from.getActualTypeArguments();
Type[] toArgs = to.getActualTypeArguments(); Type[] toArgs = to.getActualTypeArguments();
@ -322,55 +309,54 @@ public class TypeToken<T> {
return false; return false;
} }
private static AssertionError buildUnexpectedTypeError( private static AssertionError buildUnexpectedTypeError(Type token, Class<?>... expected) {
Type token, Class<?>... expected) {
// Build exception message // Build exception message
StringBuilder exceptionMessage = StringBuilder exceptionMessage = new StringBuilder("Unexpected type. Expected one of: ");
new StringBuilder("Unexpected type. Expected one of: ");
for (Class<?> clazz : expected) { for (Class<?> clazz : expected) {
exceptionMessage.append(clazz.getName()).append(", "); exceptionMessage.append(clazz.getName()).append(", ");
} }
exceptionMessage.append("but got: ").append(token.getClass().getName()) exceptionMessage
.append(", for type token: ").append(token.toString()).append('.'); .append("but got: ")
.append(token.getClass().getName())
.append(", for type token: ")
.append(token.toString())
.append('.');
return new AssertionError(exceptionMessage.toString()); return new AssertionError(exceptionMessage.toString());
} }
/** /**
* Checks if two types are the same or are equivalent under a variable mapping * Checks if two types are the same or are equivalent under a variable mapping given in the type
* given in the type map that was provided. * map that was provided.
*/ */
private static boolean matches(Type from, Type to, Map<String, Type> typeMap) { private static boolean matches(Type from, Type to, Map<String, Type> typeMap) {
return to.equals(from) return to.equals(from)
|| (from instanceof TypeVariable || (from instanceof TypeVariable
&& to.equals(typeMap.get(((TypeVariable<?>) from).getName()))); && to.equals(typeMap.get(((TypeVariable<?>) from).getName())));
} }
@Override public final int hashCode() { @Override
public final int hashCode() {
return this.hashCode; return this.hashCode;
} }
@Override public final boolean equals(Object o) { @Override
return o instanceof TypeToken<?> public final boolean equals(Object o) {
&& $Gson$Types.equals(type, ((TypeToken<?>) o).type); return o instanceof TypeToken<?> && $Gson$Types.equals(type, ((TypeToken<?>) o).type);
} }
@Override public final String toString() { @Override
public final String toString() {
return $Gson$Types.typeToString(type); return $Gson$Types.typeToString(type);
} }
/** /** Gets type literal for the given {@code Type} instance. */
* Gets type literal for the given {@code Type} instance.
*/
public static TypeToken<?> get(Type type) { public static TypeToken<?> get(Type type) {
return new TypeToken<>(type); return new TypeToken<>(type);
} }
/** /** Gets type literal for the given {@code Class} instance. */
* Gets type literal for the given {@code Class} instance.
*/
public static <T> TypeToken<T> get(Class<T> type) { public static <T> TypeToken<T> get(Class<T> type) {
return new TypeToken<>(type); return new TypeToken<>(type);
} }
@ -380,20 +366,21 @@ public class TypeToken<T> {
* {@code rawType}. This is mainly intended for situations where the type arguments are not * {@code rawType}. This is mainly intended for situations where the type arguments are not
* available at compile time. The following example shows how a type token for {@code Map<K, V>} * available at compile time. The following example shows how a type token for {@code Map<K, V>}
* can be created: * can be created:
*
* <pre>{@code * <pre>{@code
* Class<K> keyClass = ...; * Class<K> keyClass = ...;
* Class<V> valueClass = ...; * Class<V> valueClass = ...;
* TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass); * TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass);
* }</pre> * }</pre>
*
* As seen here the result is a {@code TypeToken<?>}; this method cannot provide any type-safety, * As seen here the result is a {@code TypeToken<?>}; this method cannot provide any type-safety,
* and care must be taken to pass in the correct number of type arguments. * and care must be taken to pass in the correct number of type arguments.
* *
* <p>If {@code rawType} is a non-generic class and no type arguments are provided, this method * <p>If {@code rawType} is a non-generic class and no type arguments are provided, this method
* simply delegates to {@link #get(Class)} and creates a {@code TypeToken(Class)}. * simply delegates to {@link #get(Class)} and creates a {@code TypeToken(Class)}.
* *
* @throws IllegalArgumentException * @throws IllegalArgumentException If {@code rawType} is not of type {@code Class}, or if the
* If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for * type arguments are invalid for the raw type
* the raw type
*/ */
public static TypeToken<?> getParameterized(Type rawType, Type... typeArguments) { public static TypeToken<?> getParameterized(Type rawType, Type... typeArguments) {
Objects.requireNonNull(rawType); Objects.requireNonNull(rawType);
@ -411,8 +398,12 @@ public class TypeToken<T> {
int expectedArgsCount = typeVariables.length; int expectedArgsCount = typeVariables.length;
int actualArgsCount = typeArguments.length; int actualArgsCount = typeArguments.length;
if (actualArgsCount != expectedArgsCount) { if (actualArgsCount != expectedArgsCount) {
throw new IllegalArgumentException(rawClass.getName() + " requires " + expectedArgsCount + throw new IllegalArgumentException(
" type arguments, but got " + actualArgsCount); rawClass.getName()
+ " requires "
+ expectedArgsCount
+ " type arguments, but got "
+ actualArgsCount);
} }
// For legacy reasons create a TypeToken(Class) if the type is not generic // For legacy reasons create a TypeToken(Class) if the type is not generic
@ -422,12 +413,16 @@ public class TypeToken<T> {
// Check for this here to avoid misleading exception thrown by ParameterizedTypeImpl // Check for this here to avoid misleading exception thrown by ParameterizedTypeImpl
if ($Gson$Types.requiresOwnerType(rawType)) { if ($Gson$Types.requiresOwnerType(rawType)) {
throw new IllegalArgumentException("Raw type " + rawClass.getName() + " is not supported because" throw new IllegalArgumentException(
"Raw type "
+ rawClass.getName()
+ " is not supported because"
+ " it requires specifying an owner type"); + " it requires specifying an owner type");
} }
for (int i = 0; i < expectedArgsCount; i++) { for (int i = 0; i < expectedArgsCount; i++) {
Type typeArgument = Objects.requireNonNull(typeArguments[i], "Type argument must not be null"); Type typeArgument =
Objects.requireNonNull(typeArguments[i], "Type argument must not be null");
Class<?> rawTypeArgument = $Gson$Types.getRawType(typeArgument); Class<?> rawTypeArgument = $Gson$Types.getRawType(typeArgument);
TypeVariable<?> typeVariable = typeVariables[i]; TypeVariable<?> typeVariable = typeVariables[i];
@ -435,8 +430,14 @@ public class TypeToken<T> {
Class<?> rawBound = $Gson$Types.getRawType(bound); Class<?> rawBound = $Gson$Types.getRawType(bound);
if (!rawBound.isAssignableFrom(rawTypeArgument)) { if (!rawBound.isAssignableFrom(rawTypeArgument)) {
throw new IllegalArgumentException("Type argument " + typeArgument + " does not satisfy bounds" throw new IllegalArgumentException(
+ " for type variable " + typeVariable + " declared by " + rawType); "Type argument "
+ typeArgument
+ " does not satisfy bounds"
+ " for type variable "
+ typeVariable
+ " declared by "
+ rawType);
} }
} }
} }

View File

@ -30,54 +30,56 @@ import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
/** /**
* Reads a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>) * Reads a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>) encoded value as a
* encoded value as a stream of tokens. This stream includes both literal * stream of tokens. This stream includes both literal values (strings, numbers, booleans, and
* values (strings, numbers, booleans, and nulls) as well as the begin and * nulls) as well as the begin and end delimiters of objects and arrays. The tokens are traversed in
* end delimiters of objects and arrays. The tokens are traversed in * depth-first order, the same order that they appear in the JSON document. Within JSON objects,
* depth-first order, the same order that they appear in the JSON document. * name/value pairs are represented by a single token.
* Within JSON objects, name/value pairs are represented by a single token.
* *
* <h2>Parsing JSON</h2> * <h2>Parsing JSON</h2>
* To create a recursive descent parser for your own JSON streams, first create
* an entry point method that creates a {@code JsonReader}.
* *
* <p>Next, create handler methods for each structure in your JSON text. You'll * To create a recursive descent parser for your own JSON streams, first create an entry point
* need a method for each object type and for each array type. * method that creates a {@code JsonReader}.
*
* <p>Next, create handler methods for each structure in your JSON text. You'll need a method for
* each object type and for each array type.
*
* <ul> * <ul>
* <li>Within <strong>array handling</strong> methods, first call {@link * <li>Within <strong>array handling</strong> methods, first call {@link #beginArray} to consume
* #beginArray} to consume the array's opening bracket. Then create a * the array's opening bracket. Then create a while loop that accumulates values, terminating
* while loop that accumulates values, terminating when {@link #hasNext} * when {@link #hasNext} is false. Finally, read the array's closing bracket by calling {@link
* is false. Finally, read the array's closing bracket by calling {@link
* #endArray}. * #endArray}.
* <li>Within <strong>object handling</strong> methods, first call {@link * <li>Within <strong>object handling</strong> methods, first call {@link #beginObject} to consume
* #beginObject} to consume the object's opening brace. Then create a * the object's opening brace. Then create a while loop that assigns values to local variables
* while loop that assigns values to local variables based on their name. * based on their name. This loop should terminate when {@link #hasNext} is false. Finally,
* This loop should terminate when {@link #hasNext} is false. Finally,
* read the object's closing brace by calling {@link #endObject}. * read the object's closing brace by calling {@link #endObject}.
* </ul> * </ul>
* <p>When a nested object or array is encountered, delegate to the
* corresponding handler method.
* *
* <p>When an unknown name is encountered, strict parsers should fail with an * <p>When a nested object or array is encountered, delegate to the corresponding handler method.
* exception. Lenient parsers should call {@link #skipValue()} to recursively
* skip the value's nested tokens, which may otherwise conflict.
* *
* <p>If a value may be null, you should first check using {@link #peek()}. * <p>When an unknown name is encountered, strict parsers should fail with an exception. Lenient
* Null literals can be consumed using either {@link #nextNull()} or {@link * parsers should call {@link #skipValue()} to recursively skip the value's nested tokens, which may
* #skipValue()}. * otherwise conflict.
*
* <p>If a value may be null, you should first check using {@link #peek()}. Null literals can be
* consumed using either {@link #nextNull()} or {@link #skipValue()}.
* *
* <h2>Configuration</h2> * <h2>Configuration</h2>
*
* The behavior of this reader can be customized with the following methods: * The behavior of this reader can be customized with the following methods:
*
* <ul> * <ul>
* <li>{@link #setStrictness(Strictness)}, the default is {@link Strictness#LEGACY_STRICT} * <li>{@link #setStrictness(Strictness)}, the default is {@link Strictness#LEGACY_STRICT}
* </ul> * </ul>
* *
* The default configuration of {@code JsonReader} instances used internally by * The default configuration of {@code JsonReader} instances used internally by the {@link Gson}
* the {@link Gson} class differs, and can be adjusted with the various * class differs, and can be adjusted with the various {@link GsonBuilder} methods.
* {@link GsonBuilder} methods.
* *
* <h2>Example</h2> * <h2>Example</h2>
* Suppose we'd like to parse a stream of messages such as the following: <pre> {@code *
* Suppose we'd like to parse a stream of messages such as the following:
*
* <pre>{@code
* [ * [
* { * {
* "id": 912345678901, * "id": 912345678901,
@ -97,9 +99,12 @@ import java.util.Objects;
* "followers_count": 2 * "followers_count": 2
* } * }
* } * }
* ]}</pre> * ]
* This code implements the parser for the above structure: <pre> {@code * }</pre>
* *
* This code implements the parser for the above structure:
*
* <pre>{@code
* public List<Message> readJsonStream(InputStream in) throws IOException { * public List<Message> readJsonStream(InputStream in) throws IOException {
* JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); * JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
* try { * try {
@ -173,32 +178,32 @@ import java.util.Objects;
* } * }
* reader.endObject(); * reader.endObject();
* return new User(username, followersCount); * return new User(username, followersCount);
* }}</pre> * }
* }</pre>
* *
* <h2>Number Handling</h2> * <h2>Number Handling</h2>
* This reader permits numeric values to be read as strings and string values to *
* be read as numbers. For example, both elements of the JSON array {@code * This reader permits numeric values to be read as strings and string values to be read as numbers.
* [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}. * For example, both elements of the JSON array {@code [1, "1"]} may be read using either {@link
* This behavior is intended to prevent lossy numeric conversions: double is * #nextInt} or {@link #nextString}. This behavior is intended to prevent lossy numeric conversions:
* JavaScript's only numeric type and very large values like {@code * double is JavaScript's only numeric type and very large values like {@code 9007199254740993}
* 9007199254740993} cannot be represented exactly on that platform. To minimize * cannot be represented exactly on that platform. To minimize precision loss, extremely large
* precision loss, extremely large values should be written and read as strings * values should be written and read as strings in JSON.
* in JSON.
* *
* <h2 id="nonexecuteprefix">Non-Execute Prefix</h2> * <h2 id="nonexecuteprefix">Non-Execute Prefix</h2>
*
* Web servers that serve private data using JSON may be vulnerable to <a * Web servers that serve private data using JSON may be vulnerable to <a
* href="http://en.wikipedia.org/wiki/JSON#Cross-site_request_forgery">Cross-site * href="http://en.wikipedia.org/wiki/JSON#Cross-site_request_forgery">Cross-site request
* request forgery</a> attacks. In such an attack, a malicious site gains access * forgery</a> attacks. In such an attack, a malicious site gains access to a private JSON file by
* to a private JSON file by executing it with an HTML {@code <script>} tag. * executing it with an HTML {@code <script>} tag.
* *
* <p>Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable * <p>Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable by {@code <script>}
* by {@code <script>} tags, disarming the attack. Since the prefix is malformed * tags, disarming the attack. Since the prefix is malformed JSON, strict parsing fails when it is
* JSON, strict parsing fails when it is encountered. This class permits the * encountered. This class permits the non-execute prefix when {@linkplain
* non-execute prefix when {@linkplain #setStrictness(Strictness) lenient parsing} is * #setStrictness(Strictness) lenient parsing} is enabled.
* enabled.
* *
* <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances of this class are
* of this class are not thread safe. * not thread safe.
* *
* @author Jesse Wilson * @author Jesse Wilson
* @since 1.6 * @since 1.6
@ -217,13 +222,17 @@ public class JsonReader implements Closeable {
private static final int PEEKED_SINGLE_QUOTED = 8; private static final int PEEKED_SINGLE_QUOTED = 8;
private static final int PEEKED_DOUBLE_QUOTED = 9; private static final int PEEKED_DOUBLE_QUOTED = 9;
private static final int PEEKED_UNQUOTED = 10; private static final int PEEKED_UNQUOTED = 10;
/** When this is returned, the string value is stored in peekedString. */ /** When this is returned, the string value is stored in peekedString. */
private static final int PEEKED_BUFFERED = 11; private static final int PEEKED_BUFFERED = 11;
private static final int PEEKED_SINGLE_QUOTED_NAME = 12; private static final int PEEKED_SINGLE_QUOTED_NAME = 12;
private static final int PEEKED_DOUBLE_QUOTED_NAME = 13; private static final int PEEKED_DOUBLE_QUOTED_NAME = 13;
private static final int PEEKED_UNQUOTED_NAME = 14; private static final int PEEKED_UNQUOTED_NAME = 14;
/** When this is returned, the integer value is stored in peekedLong. */ /** When this is returned, the integer value is stored in peekedLong. */
private static final int PEEKED_LONG = 15; private static final int PEEKED_LONG = 15;
private static final int PEEKED_NUMBER = 16; private static final int PEEKED_NUMBER = 16;
private static final int PEEKED_EOF = 17; private static final int PEEKED_EOF = 17;
@ -243,13 +252,14 @@ public class JsonReader implements Closeable {
private Strictness strictness = Strictness.LEGACY_STRICT; private Strictness strictness = Strictness.LEGACY_STRICT;
static final int BUFFER_SIZE = 1024; static final int BUFFER_SIZE = 1024;
/** /**
* Use a manual buffer to easily read and unread upcoming characters, and * Use a manual buffer to easily read and unread upcoming characters, and also so we can create
* also so we can create strings without an intermediate StringBuilder. * strings without an intermediate StringBuilder. We decode literals directly out of this buffer,
* We decode literals directly out of this buffer, so it must be at least as * so it must be at least as long as the longest token that can be reported as a number.
* long as the longest token that can be reported as a number.
*/ */
private final char[] buffer = new char[BUFFER_SIZE]; private final char[] buffer = new char[BUFFER_SIZE];
private int pos = 0; private int pos = 0;
private int limit = 0; private int limit = 0;
@ -259,21 +269,20 @@ public class JsonReader implements Closeable {
int peeked = PEEKED_NONE; int peeked = PEEKED_NONE;
/** /**
* A peeked value that was composed entirely of digits with an optional * A peeked value that was composed entirely of digits with an optional leading dash. Positive
* leading dash. Positive values may not have a leading 0. * values may not have a leading 0.
*/ */
private long peekedLong; private long peekedLong;
/** /**
* The number of characters in a peeked number literal. Increment 'pos' by * The number of characters in a peeked number literal. Increment 'pos' by this after reading a
* this after reading a number. * number.
*/ */
private int peekedNumberLength; private int peekedNumberLength;
/** /**
* A peeked string that should be parsed on the next double, long or string. * A peeked string that should be parsed on the next double, long or string. This is populated
* This is populated before a numeric value is parsed and used if that parsing * before a numeric value is parsed and used if that parsing fails.
* fails.
*/ */
private String peekedString; private String peekedString;
@ -282,6 +291,7 @@ public class JsonReader implements Closeable {
*/ */
private int[] stack = new int[32]; private int[] stack = new int[32];
private int stackSize = 0; private int stackSize = 0;
{ {
stack[stackSize++] = JsonScope.EMPTY_DOCUMENT; stack[stackSize++] = JsonScope.EMPTY_DOCUMENT;
} }
@ -297,9 +307,7 @@ public class JsonReader implements Closeable {
private String[] pathNames = new String[32]; private String[] pathNames = new String[32];
private int[] pathIndices = new int[32]; private int[] pathIndices = new int[32];
/** /** Creates a new instance that reads a JSON-encoded stream from {@code in}. */
* Creates a new instance that reads a JSON-encoded stream from {@code in}.
*/
public JsonReader(Reader in) { public JsonReader(Reader in) {
this.in = Objects.requireNonNull(in, "in == null"); this.in = Objects.requireNonNull(in, "in == null");
} }
@ -307,17 +315,20 @@ public class JsonReader implements Closeable {
/** /**
* Sets the strictness of this reader. * Sets the strictness of this reader.
* *
* @deprecated Please use {@link #setStrictness(Strictness)} instead. * @deprecated Please use {@link #setStrictness(Strictness)} instead. {@code
* {@code JsonReader.setLenient(true)} should be replaced by {@code JsonReader.setStrictness(Strictness.LENIENT)} * JsonReader.setLenient(true)} should be replaced by {@code
* and {@code JsonReader.setLenient(false)} should be replaced by {@code JsonReader.setStrictness(Strictness.LEGACY_STRICT)}.<br> * JsonReader.setStrictness(Strictness.LENIENT)} and {@code JsonReader.setLenient(false)}
* However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead. * should be replaced by {@code JsonReader.setStrictness(Strictness.LEGACY_STRICT)}.<br>
* * However, if you used {@code setLenient(false)} before, you might prefer {@link
* @param lenient whether this reader should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}. * Strictness#STRICT} now instead.
* If false, the strictness is set to {@link Strictness#LEGACY_STRICT}. * @param lenient whether this reader should be lenient. If true, the strictness is set to {@link
* Strictness#LENIENT}. If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
* @see #setStrictness(Strictness) * @see #setStrictness(Strictness)
*/ */
@Deprecated @Deprecated
@SuppressWarnings("InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new Strictness.STRICT // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new
// Strictness.STRICT
@SuppressWarnings("InlineMeSuggester")
public final void setLenient(boolean lenient) { public final void setLenient(boolean lenient) {
setStrictness(lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT); setStrictness(lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT);
} }
@ -334,53 +345,51 @@ public class JsonReader implements Closeable {
/** /**
* Configures how liberal this parser is in what it accepts. * Configures how liberal this parser is in what it accepts.
* *
* <p>In {@linkplain Strictness#STRICT strict} mode, the * <p>In {@linkplain Strictness#STRICT strict} mode, the parser only accepts JSON in accordance
* parser only accepts JSON in accordance with <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>. * with <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>. In {@linkplain
* In {@linkplain Strictness#LEGACY_STRICT legacy strict} mode (the default), only JSON in accordance with the * Strictness#LEGACY_STRICT legacy strict} mode (the default), only JSON in accordance with the
* RFC 8259 is accepted, with a few exceptions denoted below for backwards compatibility reasons. * RFC 8259 is accepted, with a few exceptions denoted below for backwards compatibility reasons.
* In {@linkplain Strictness#LENIENT lenient} mode, all sort of non-spec compliant JSON is accepted (see below).</p> * In {@linkplain Strictness#LENIENT lenient} mode, all sort of non-spec compliant JSON is
* accepted (see below).
* *
* <dl> * <dl>
* <dt>{@link Strictness#STRICT}</dt> * <dt>{@link Strictness#STRICT}
* <dd> * <dd>In strict mode, only input compliant with RFC 8259 is accepted.
* In strict mode, only input compliant with RFC 8259 is accepted. * <dt>{@link Strictness#LEGACY_STRICT}
* </dd> * <dd>In legacy strict mode, the following departures from RFC 8259 are accepted:
* <dt>{@link Strictness#LEGACY_STRICT}</dt>
* <dd>
* In legacy strict mode, the following departures from RFC 8259 are accepted:
* <ul> * <ul>
* <li>JsonReader allows the literals {@code true}, {@code false} and {@code null} * <li>JsonReader allows the literals {@code true}, {@code false} and {@code null} to have
* to have any capitalization, for example {@code fAlSe} or {@code NULL} * any capitalization, for example {@code fAlSe} or {@code NULL}
* <li>JsonReader supports the escape sequence {@code \'}, representing a {@code '} (single-quote) * <li>JsonReader supports the escape sequence {@code \'}, representing a {@code '}
* (single-quote)
* <li>JsonReader supports the escape sequence <code>\<i>LF</i></code> (with {@code LF} * <li>JsonReader supports the escape sequence <code>\<i>LF</i></code> (with {@code LF}
* being the Unicode character {@code U+000A}), resulting in a {@code LF} within the * being the Unicode character {@code U+000A}), resulting in a {@code LF} within the
* read JSON string * read JSON string
* <li>JsonReader allows unescaped control characters ({@code U+0000} through {@code U+001F}) * <li>JsonReader allows unescaped control characters ({@code U+0000} through {@code
* U+001F})
* </ul> * </ul>
* </dd> * <dt>{@link Strictness#LENIENT}
* <dt>{@link Strictness#LENIENT}</dt> * <dd>In lenient mode, all input that is accepted in legacy strict mode is accepted in addition
* <dd> * to the following departures from RFC 8259:
* In lenient mode, all input that is accepted in legacy strict mode is accepted in addition to the following
* departures from RFC 8259:
* <ul> * <ul>
* <li>Streams that start with the <a href="#nonexecuteprefix">non-execute prefix</a>, {@code ")]}'\n"} * <li>Streams that start with the <a href="#nonexecuteprefix">non-execute prefix</a>,
* <li>Streams that include multiple top-level values. With legacy strict or strict parsing, * {@code ")]}'\n"}
* each stream must contain exactly one top-level value. * <li>Streams that include multiple top-level values. With legacy strict or strict
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities} represented by * parsing, each stream must contain exactly one top-level value.
* {@code NaN} and {@code (-)Infinity} respectively. * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
* <li>End of line comments starting with {@code //} or {@code #} and ending with a newline character. * infinities} represented by {@code NaN} and {@code (-)Infinity} respectively.
* <li>C-style comments starting with {@code /*} and ending with * <li>End of line comments starting with {@code //} or {@code #} and ending with a
* {@code *}{@code /}. Such comments may not be nested. * newline character.
* <li>C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such
* comments may not be nested.
* <li>Names that are unquoted or {@code 'single quoted'}. * <li>Names that are unquoted or {@code 'single quoted'}.
* <li>Strings that are unquoted or {@code 'single quoted'}. * <li>Strings that are unquoted or {@code 'single quoted'}.
* <li>Array elements separated by {@code ;} instead of {@code ,}. * <li>Array elements separated by {@code ;} instead of {@code ,}.
* <li>Unnecessary array separators. These are interpreted as if null * <li>Unnecessary array separators. These are interpreted as if null was the omitted
* was the omitted value. * value.
* <li>Names and values separated by {@code =} or {@code =>} instead of * <li>Names and values separated by {@code =} or {@code =>} instead of {@code :}.
* {@code :}.
* <li>Name/value pairs separated by {@code ;} instead of {@code ,}. * <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
* </ul> * </ul>
* </dd>
* </dl> * </dl>
* *
* @param strictness the new strictness value of this reader. May not be {@code null}. * @param strictness the new strictness value of this reader. May not be {@code null}.
@ -400,9 +409,10 @@ public class JsonReader implements Closeable {
public final Strictness getStrictness() { public final Strictness getStrictness() {
return strictness; return strictness;
} }
/** /**
* Consumes the next token from the JSON stream and asserts that it is the * Consumes the next token from the JSON stream and asserts that it is the beginning of a new
* beginning of a new array. * array.
*/ */
public void beginArray() throws IOException { public void beginArray() throws IOException {
int p = peeked; int p = peeked;
@ -419,8 +429,8 @@ public class JsonReader implements Closeable {
} }
/** /**
* Consumes the next token from the JSON stream and asserts that it is the * Consumes the next token from the JSON stream and asserts that it is the end of the current
* end of the current array. * array.
*/ */
public void endArray() throws IOException { public void endArray() throws IOException {
int p = peeked; int p = peeked;
@ -437,8 +447,8 @@ public class JsonReader implements Closeable {
} }
/** /**
* Consumes the next token from the JSON stream and asserts that it is the * Consumes the next token from the JSON stream and asserts that it is the beginning of a new
* beginning of a new object. * object.
*/ */
public void beginObject() throws IOException { public void beginObject() throws IOException {
int p = peeked; int p = peeked;
@ -454,8 +464,8 @@ public class JsonReader implements Closeable {
} }
/** /**
* Consumes the next token from the JSON stream and asserts that it is the * Consumes the next token from the JSON stream and asserts that it is the end of the current
* end of the current object. * object.
*/ */
public void endObject() throws IOException { public void endObject() throws IOException {
int p = peeked; int p = peeked;
@ -472,9 +482,7 @@ public class JsonReader implements Closeable {
} }
} }
/** /** Returns true if the current array or object has another element. */
* Returns true if the current array or object has another element.
*/
public boolean hasNext() throws IOException { public boolean hasNext() throws IOException {
int p = peeked; int p = peeked;
if (p == PEEKED_NONE) { if (p == PEEKED_NONE) {
@ -483,9 +491,7 @@ public class JsonReader implements Closeable {
return p != PEEKED_END_OBJECT && p != PEEKED_END_ARRAY && p != PEEKED_EOF; return p != PEEKED_END_OBJECT && p != PEEKED_END_ARRAY && p != PEEKED_EOF;
} }
/** /** Returns the type of the next token without consuming it. */
* Returns the type of the next token without consuming it.
*/
public JsonToken peek() throws IOException { public JsonToken peek() throws IOException {
int p = peeked; int p = peeked;
if (p == PEEKED_NONE) { if (p == PEEKED_NONE) {
@ -702,8 +708,7 @@ public class JsonReader implements Closeable {
} }
} }
if ((pos + length < limit || fillBuffer(length + 1)) if ((pos + length < limit || fillBuffer(length + 1)) && isLiteral(buffer[pos + length])) {
&& isLiteral(buffer[pos + length])) {
return PEEKED_NONE; // Don't match trues, falsey or nullsoft! return PEEKED_NONE; // Don't match trues, falsey or nullsoft!
} }
@ -790,7 +795,8 @@ public class JsonReader implements Closeable {
return PEEKED_NONE; // Leading '0' prefix is not allowed (since it could be octal). return PEEKED_NONE; // Leading '0' prefix is not allowed (since it could be octal).
} }
long newValue = value * 10 - (c - '0'); long newValue = value * 10 - (c - '0');
fitsInLong &= value > MIN_INCOMPLETE_INTEGER fitsInLong &=
value > MIN_INCOMPLETE_INTEGER
|| (value == MIN_INCOMPLETE_INTEGER && newValue < value); || (value == MIN_INCOMPLETE_INTEGER && newValue < value);
value = newValue; value = newValue;
} else if (last == NUMBER_CHAR_DECIMAL) { } else if (last == NUMBER_CHAR_DECIMAL) {
@ -804,11 +810,15 @@ public class JsonReader implements Closeable {
// We've read a complete number. Decide if it's a PEEKED_LONG or a PEEKED_NUMBER. // We've read a complete number. Decide if it's a PEEKED_LONG or a PEEKED_NUMBER.
// Don't store -0 as long; user might want to read it as double -0.0 // Don't store -0 as long; user might want to read it as double -0.0
// Don't try to convert Long.MIN_VALUE to positive long; it would overflow MAX_VALUE // Don't try to convert Long.MIN_VALUE to positive long; it would overflow MAX_VALUE
if (last == NUMBER_CHAR_DIGIT && fitsInLong && (value != Long.MIN_VALUE || negative) && (value != 0 || !negative)) { if (last == NUMBER_CHAR_DIGIT
&& fitsInLong
&& (value != Long.MIN_VALUE || negative)
&& (value != 0 || !negative)) {
peekedLong = negative ? value : -value; peekedLong = negative ? value : -value;
pos += i; pos += i;
return peeked = PEEKED_LONG; return peeked = PEEKED_LONG;
} else if (last == NUMBER_CHAR_DIGIT || last == NUMBER_CHAR_FRACTION_DIGIT } else if (last == NUMBER_CHAR_DIGIT
|| last == NUMBER_CHAR_FRACTION_DIGIT
|| last == NUMBER_CHAR_EXP_DIGIT) { || last == NUMBER_CHAR_EXP_DIGIT) {
peekedNumberLength = i; peekedNumberLength = i;
return peeked = PEEKED_NUMBER; return peeked = PEEKED_NUMBER;
@ -846,8 +856,7 @@ public class JsonReader implements Closeable {
/** /**
* Returns the next token, a {@link JsonToken#NAME property name}, and consumes it. * Returns the next token, a {@link JsonToken#NAME property name}, and consumes it.
* *
* @throws IOException if the next token in the stream is not a property * @throws IOException if the next token in the stream is not a property name.
* name.
*/ */
public String nextName() throws IOException { public String nextName() throws IOException {
int p = peeked; int p = peeked;
@ -870,12 +879,10 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns the {@link JsonToken#STRING string} value of the next token, * Returns the {@link JsonToken#STRING string} value of the next token, consuming it. If the next
* consuming it. If the next token is a number, this method will return its * token is a number, this method will return its string form.
* string form.
* *
* @throws IllegalStateException if the next token is not a string or if * @throws IllegalStateException if the next token is not a string or if this reader is closed.
* this reader is closed.
*/ */
public String nextString() throws IOException { public String nextString() throws IOException {
int p = peeked; int p = peeked;
@ -906,11 +913,9 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns the {@link JsonToken#BOOLEAN boolean} value of the next token, * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token, consuming it.
* consuming it.
* *
* @throws IllegalStateException if the next token is not a boolean or if * @throws IllegalStateException if the next token is not a boolean or if this reader is closed.
* this reader is closed.
*/ */
public boolean nextBoolean() throws IOException { public boolean nextBoolean() throws IOException {
int p = peeked; int p = peeked;
@ -930,11 +935,9 @@ public class JsonReader implements Closeable {
} }
/** /**
* Consumes the next token from the JSON stream and asserts that it is a * Consumes the next token from the JSON stream and asserts that it is a literal null.
* literal null.
* *
* @throws IllegalStateException if the next token is not null or if this * @throws IllegalStateException if the next token is not null or if this reader is closed.
* reader is closed.
*/ */
public void nextNull() throws IOException { public void nextNull() throws IOException {
int p = peeked; int p = peeked;
@ -950,15 +953,14 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns the {@link JsonToken#NUMBER double} value of the next token, * Returns the {@link JsonToken#NUMBER double} value of the next token, consuming it. If the next
* consuming it. If the next token is a string, this method will attempt to * token is a string, this method will attempt to parse it as a double using {@link
* parse it as a double using {@link Double#parseDouble(String)}. * Double#parseDouble(String)}.
* *
* @throws IllegalStateException if the next token is not a literal value. * @throws IllegalStateException if the next token is not a literal value.
* @throws NumberFormatException if the next literal value cannot be parsed * @throws NumberFormatException if the next literal value cannot be parsed as a double.
* as a double. * @throws MalformedJsonException if the next literal value is NaN or Infinity and this reader is
* @throws MalformedJsonException if the next literal value is NaN or Infinity * not {@link #setStrictness(Strictness) lenient}.
* and this reader is not {@link #setStrictness(Strictness) lenient}.
*/ */
public double nextDouble() throws IOException { public double nextDouble() throws IOException {
int p = peeked; int p = peeked;
@ -995,14 +997,13 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns the {@link JsonToken#NUMBER long} value of the next token, * Returns the {@link JsonToken#NUMBER long} value of the next token, consuming it. If the next
* consuming it. If the next token is a string, this method will attempt to * token is a string, this method will attempt to parse it as a long. If the next token's numeric
* parse it as a long. If the next token's numeric value cannot be exactly * value cannot be exactly represented by a Java {@code long}, this method throws.
* represented by a Java {@code long}, this method throws.
* *
* @throws IllegalStateException if the next token is not a literal value. * @throws IllegalStateException if the next token is not a literal value.
* @throws NumberFormatException if the next literal value cannot be parsed * @throws NumberFormatException if the next literal value cannot be parsed as a number, or
* as a number, or exactly represented as a long. * exactly represented as a long.
*/ */
public long nextLong() throws IOException { public long nextLong() throws IOException {
int p = peeked; int p = peeked;
@ -1050,14 +1051,12 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns the string up to but not including {@code quote}, unescaping any * Returns the string up to but not including {@code quote}, unescaping any character escape
* character escape sequences encountered along the way. The opening quote * sequences encountered along the way. The opening quote should have already been read. This
* should have already been read. This consumes the closing quote, but does * consumes the closing quote, but does not include it in the returned string.
* not include it in the returned string.
* *
* @param quote either ' or ". * @param quote either ' or ".
* @throws NumberFormatException if any unicode escape sequences are * @throws NumberFormatException if any unicode escape sequences are malformed.
* malformed.
*/ */
private String nextQuotedValue(char quote) throws IOException { private String nextQuotedValue(char quote) throws IOException {
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access. // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
@ -1071,9 +1070,11 @@ public class JsonReader implements Closeable {
while (p < l) { while (p < l) {
int c = buffer[p++]; int c = buffer[p++];
// In strict mode, throw an exception when meeting unescaped control characters (U+0000 through U+001F) // In strict mode, throw an exception when meeting unescaped control characters (U+0000
// through U+001F)
if (strictness == Strictness.STRICT && c < 0x20) { if (strictness == Strictness.STRICT && c < 0x20) {
throw syntaxError("Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode"); throw syntaxError(
"Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode");
} else if (c == quote) { } else if (c == quote) {
pos = p; pos = p;
int len = p - start - 1; int len = p - start - 1;
@ -1113,9 +1114,7 @@ public class JsonReader implements Closeable {
} }
} }
/** /** Returns an unquoted value as a string. */
* Returns an unquoted value as a string.
*/
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
private String nextUnquotedValue() throws IOException { private String nextUnquotedValue() throws IOException {
StringBuilder builder = null; StringBuilder builder = null;
@ -1157,7 +1156,7 @@ public class JsonReader implements Closeable {
// use a StringBuilder when the value is too long. This is too long to be a number! // use a StringBuilder when the value is too long. This is too long to be a number!
if (builder == null) { if (builder == null) {
builder = new StringBuilder(Math.max(i,16)); builder = new StringBuilder(Math.max(i, 16));
} }
builder.append(buffer, pos, i); builder.append(buffer, pos, i);
pos += i; pos += i;
@ -1167,7 +1166,8 @@ public class JsonReader implements Closeable {
} }
} }
String result = (null == builder) ? new String(buffer, pos, i) : builder.append(buffer, pos, i).toString(); String result =
(null == builder) ? new String(buffer, pos, i) : builder.append(buffer, pos, i).toString();
pos += i; pos += i;
return result; return result;
} }
@ -1231,14 +1231,13 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns the {@link JsonToken#NUMBER int} value of the next token, * Returns the {@link JsonToken#NUMBER int} value of the next token, consuming it. If the next
* consuming it. If the next token is a string, this method will attempt to * token is a string, this method will attempt to parse it as an int. If the next token's numeric
* parse it as an int. If the next token's numeric value cannot be exactly * value cannot be exactly represented by a Java {@code int}, this method throws.
* represented by a Java {@code int}, this method throws.
* *
* @throws IllegalStateException if the next token is not a literal value. * @throws IllegalStateException if the next token is not a literal value.
* @throws NumberFormatException if the next literal value cannot be parsed * @throws NumberFormatException if the next literal value cannot be parsed as a number, or
* as a number, or exactly represented as an int. * exactly represented as an int.
*/ */
public int nextInt() throws IOException { public int nextInt() throws IOException {
int p = peeked; int p = peeked;
@ -1290,10 +1289,9 @@ public class JsonReader implements Closeable {
return result; return result;
} }
/** /** Closes this JSON reader and the underlying {@link Reader}. */
* Closes this JSON reader and the underlying {@link Reader}. @Override
*/ public void close() throws IOException {
@Override public void close() throws IOException {
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
stack[0] = JsonScope.CLOSED; stack[0] = JsonScope.CLOSED;
stackSize = 1; stackSize = 1;
@ -1301,18 +1299,19 @@ public class JsonReader implements Closeable {
} }
/** /**
* Skips the next value recursively. This method is intended for use when * Skips the next value recursively. This method is intended for use when the JSON token stream
* the JSON token stream contains unrecognized or unhandled values. * contains unrecognized or unhandled values.
* *
* <p>The behavior depends on the type of the next JSON token: * <p>The behavior depends on the type of the next JSON token:
*
* <ul> * <ul>
* <li>Start of a JSON array or object: It and all of its nested values are skipped.</li> * <li>Start of a JSON array or object: It and all of its nested values are skipped.
* <li>Primitive value (for example a JSON number): The primitive value is skipped.</li> * <li>Primitive value (for example a JSON number): The primitive value is skipped.
* <li>Property name: Only the name but not the value of the property is skipped. * <li>Property name: Only the name but not the value of the property is skipped. {@code
* {@code skipValue()} has to be called again to skip the property value as well.</li> * skipValue()} has to be called again to skip the property value as well.
* <li>End of a JSON array or object: Only this end token is skipped.</li> * <li>End of a JSON array or object: Only this end token is skipped.
* <li>End of JSON document: Skipping has no effect, the next token continues to be the * <li>End of JSON document: Skipping has no effect, the next token continues to be the end of
* end of the document.</li> * the document.
* </ul> * </ul>
*/ */
public void skipValue() throws IOException { public void skipValue() throws IOException {
@ -1337,9 +1336,11 @@ public class JsonReader implements Closeable {
count--; count--;
break; break;
case PEEKED_END_OBJECT: case PEEKED_END_OBJECT:
// Only update when object end is explicitly skipped, otherwise stack is not updated anyways // Only update when object end is explicitly skipped, otherwise stack is not updated
// anyways
if (count == 0) { if (count == 0) {
pathNames[stackSize - 1] = null; // Free the last path name so that it can be garbage collected pathNames[stackSize - 1] =
null; // Free the last path name so that it can be garbage collected
} }
stackSize--; stackSize--;
count--; count--;
@ -1380,7 +1381,8 @@ public class JsonReader implements Closeable {
case PEEKED_EOF: case PEEKED_EOF:
// Do nothing // Do nothing
return; return;
// For all other tokens there is nothing to do; token has already been consumed from underlying reader // For all other tokens there is nothing to do; token has already been consumed from
// underlying reader
} }
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
} while (count > 0); } while (count > 0);
@ -1399,9 +1401,8 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns true once {@code limit - pos >= minimum}. If the data is * Returns true once {@code limit - pos >= minimum}. If the data is exhausted before that many
* exhausted before that many characters are available, this returns * characters are available, this returns false.
* false.
*/ */
private boolean fillBuffer(int minimum) throws IOException { private boolean fillBuffer(int minimum) throws IOException {
char[] buffer = this.buffer; char[] buffer = this.buffer;
@ -1433,10 +1434,9 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns the next character in the stream that is neither whitespace nor a * Returns the next character in the stream that is neither whitespace nor a part of a comment.
* part of a comment. When this returns, the returned character is always at * When this returns, the returned character is always at {@code buffer[pos-1]}; this means the
* {@code buffer[pos-1]}; this means the caller can always push back the * caller can always push back the returned character by decrementing {@code pos}.
* returned character by decrementing {@code pos}.
*/ */
private int nextNonWhitespace(boolean throwOnEof) throws IOException { private int nextNonWhitespace(boolean throwOnEof) throws IOException {
/* /*
@ -1529,14 +1529,14 @@ public class JsonReader implements Closeable {
private void checkLenient() throws IOException { private void checkLenient() throws IOException {
if (strictness != Strictness.LENIENT) { if (strictness != Strictness.LENIENT) {
throw syntaxError("Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON"); throw syntaxError(
"Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON");
} }
} }
/** /**
* Advances the position until after the next newline character. If the line * Advances the position until after the next newline character. If the line is terminated by
* is terminated by "\r\n", the '\n' must be consumed as whitespace by the * "\r\n", the '\n' must be consumed as whitespace by the caller.
* caller.
*/ */
private void skipToEndOfLine() throws IOException { private void skipToEndOfLine() throws IOException {
while (pos < limit || fillBuffer(1)) { while (pos < limit || fillBuffer(1)) {
@ -1573,7 +1573,8 @@ public class JsonReader implements Closeable {
return false; return false;
} }
@Override public String toString() { @Override
public String toString() {
return getClass().getSimpleName() + locationString(); return getClass().getSimpleName() + locationString();
} }
@ -1614,48 +1615,47 @@ public class JsonReader implements Closeable {
} }
/** /**
* Returns a <a href="https://goessner.net/articles/JsonPath/">JSONPath</a> * Returns a <a href="https://goessner.net/articles/JsonPath/">JSONPath</a> in <i>dot-notation</i>
* in <i>dot-notation</i> to the previous (or current) location in the JSON document: * to the previous (or current) location in the JSON document:
*
* <ul> * <ul>
* <li>For JSON arrays the path points to the index of the previous element.<br> * <li>For JSON arrays the path points to the index of the previous element.<br>
* If no element has been consumed yet it uses the index 0 (even if there are no elements).</li> * If no element has been consumed yet it uses the index 0 (even if there are no elements).
* <li>For JSON objects the path points to the last property, or to the current * <li>For JSON objects the path points to the last property, or to the current property if its
* property if its name has already been consumed.</li> * name has already been consumed.
* </ul> * </ul>
* *
* <p>This method can be useful to add additional context to exception messages * <p>This method can be useful to add additional context to exception messages <i>after</i> a
* <i>after</i> a value has been consumed. * value has been consumed.
*/ */
public String getPreviousPath() { public String getPreviousPath() {
return getPath(true); return getPath(true);
} }
/** /**
* Returns a <a href="https://goessner.net/articles/JsonPath/">JSONPath</a> * Returns a <a href="https://goessner.net/articles/JsonPath/">JSONPath</a> in <i>dot-notation</i>
* in <i>dot-notation</i> to the next (or current) location in the JSON document: * to the next (or current) location in the JSON document:
*
* <ul> * <ul>
* <li>For JSON arrays the path points to the index of the next element (even * <li>For JSON arrays the path points to the index of the next element (even if there are no
* if there are no further elements).</li> * further elements).
* <li>For JSON objects the path points to the last property, or to the current * <li>For JSON objects the path points to the last property, or to the current property if its
* property if its name has already been consumed.</li> * name has already been consumed.
* </ul> * </ul>
* *
* <p>This method can be useful to add additional context to exception messages * <p>This method can be useful to add additional context to exception messages <i>before</i> a
* <i>before</i> a value is consumed, for example when the {@linkplain #peek() peeked} * value is consumed, for example when the {@linkplain #peek() peeked} token is unexpected.
* token is unexpected.
*/ */
public String getPath() { public String getPath() {
return getPath(false); return getPath(false);
} }
/** /**
* Unescapes the character identified by the character or characters that * Unescapes the character identified by the character or characters that immediately follow a
* immediately follow a backslash. The backslash '\' should have already * backslash. The backslash '\' should have already been read. This supports both Unicode escapes
* been read. This supports both Unicode escapes "u000A" and two-character * "u000A" and two-character escapes "\n".
* escapes "\n".
* *
* @throws MalformedJsonException if any Unicode escape sequences are * @throws MalformedJsonException if any Unicode escape sequences are malformed.
* malformed.
*/ */
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
private char readEscapeCharacter() throws IOException { private char readEscapeCharacter() throws IOException {
@ -1725,25 +1725,29 @@ public class JsonReader implements Closeable {
} }
/** /**
* Throws a new IO exception with the given message and a context snippet * Throws a new IO exception with the given message and a context snippet with this reader's
* with this reader's content. * content.
*/ */
private IOException syntaxError(String message) throws IOException { private IOException syntaxError(String message) throws IOException {
throw new MalformedJsonException(message + locationString() throw new MalformedJsonException(
+ "\nSee " + TroubleshootingGuide.createUrl("malformed-json")); message + locationString() + "\nSee " + TroubleshootingGuide.createUrl("malformed-json"));
} }
private IllegalStateException unexpectedTokenError(String expected) throws IOException { private IllegalStateException unexpectedTokenError(String expected) throws IOException {
JsonToken peeked = peek(); JsonToken peeked = peek();
String troubleshootingId = peeked == JsonToken.NULL String troubleshootingId =
? "adapter-not-null-safe" : "unexpected-json-structure"; peeked == JsonToken.NULL ? "adapter-not-null-safe" : "unexpected-json-structure";
return new IllegalStateException("Expected " + expected + " but was " + peek() + locationString() return new IllegalStateException(
+ "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId)); "Expected "
+ expected
+ " but was "
+ peek()
+ locationString()
+ "\nSee "
+ TroubleshootingGuide.createUrl(troubleshootingId));
} }
/** /** Consumes the non-execute prefix if it exists. */
* Consumes the non-execute prefix if it exists.
*/
private void consumeNonExecutePrefix() throws IOException { private void consumeNonExecutePrefix() throws IOException {
// fast-forward through the leading whitespace // fast-forward through the leading whitespace
int unused = nextNonWhitespace(true); int unused = nextNonWhitespace(true);
@ -1755,7 +1759,11 @@ public class JsonReader implements Closeable {
int p = pos; int p = pos;
char[] buf = buffer; char[] buf = buffer;
if (buf[p] != ')' || buf[p + 1] != ']' || buf[p + 2] != '}' || buf[p + 3] != '\'' || buf[p + 4] != '\n') { if (buf[p] != ')'
|| buf[p + 1] != ']'
|| buf[p + 2] != '}'
|| buf[p + 3] != '\''
|| buf[p + 4] != '\n') {
return; // not a security token! return; // not a security token!
} }
@ -1764,10 +1772,12 @@ public class JsonReader implements Closeable {
} }
static { static {
JsonReaderInternalAccess.INSTANCE = new JsonReaderInternalAccess() { JsonReaderInternalAccess.INSTANCE =
@Override public void promoteNameToValue(JsonReader reader) throws IOException { new JsonReaderInternalAccess() {
@Override
public void promoteNameToValue(JsonReader reader) throws IOException {
if (reader instanceof JsonTreeReader) { if (reader instanceof JsonTreeReader) {
((JsonTreeReader)reader).promoteNameToValue(); ((JsonTreeReader) reader).promoteNameToValue();
return; return;
} }
int p = reader.peeked; int p = reader.peeked;

View File

@ -24,48 +24,30 @@ package com.google.gson.stream;
*/ */
final class JsonScope { final class JsonScope {
/** /** An array with no elements requires no separators or newlines before it is closed. */
* An array with no elements requires no separators or newlines before
* it is closed.
*/
static final int EMPTY_ARRAY = 1; static final int EMPTY_ARRAY = 1;
/** /** An array with at least one value requires a comma and newline before the next element. */
* An array with at least one value requires a comma and newline before
* the next element.
*/
static final int NONEMPTY_ARRAY = 2; static final int NONEMPTY_ARRAY = 2;
/** /** An object with no name/value pairs requires no separators or newlines before it is closed. */
* An object with no name/value pairs requires no separators or newlines
* before it is closed.
*/
static final int EMPTY_OBJECT = 3; static final int EMPTY_OBJECT = 3;
/** /** An object whose most recent element is a key. The next element must be a value. */
* An object whose most recent element is a key. The next element must
* be a value.
*/
static final int DANGLING_NAME = 4; static final int DANGLING_NAME = 4;
/** /**
* An object with at least one name/value pair requires a comma and * An object with at least one name/value pair requires a comma and newline before the next
* newline before the next element. * element.
*/ */
static final int NONEMPTY_OBJECT = 5; static final int NONEMPTY_OBJECT = 5;
/** /** No object or array has been started. */
* No object or array has been started.
*/
static final int EMPTY_DOCUMENT = 6; static final int EMPTY_DOCUMENT = 6;
/** /** A document with at an array or object. */
* A document with at an array or object.
*/
static final int NONEMPTY_DOCUMENT = 7; static final int NONEMPTY_DOCUMENT = 7;
/** /** A document that's been closed and cannot be accessed. */
* A document that's been closed and cannot be accessed.
*/
static final int CLOSED = 8; static final int CLOSED = 8;
} }

View File

@ -25,61 +25,52 @@ package com.google.gson.stream;
public enum JsonToken { public enum JsonToken {
/** /**
* The opening of a JSON array. Written using {@link JsonWriter#beginArray} * The opening of a JSON array. Written using {@link JsonWriter#beginArray} and read using {@link
* and read using {@link JsonReader#beginArray}. * JsonReader#beginArray}.
*/ */
BEGIN_ARRAY, BEGIN_ARRAY,
/** /**
* The closing of a JSON array. Written using {@link JsonWriter#endArray} * The closing of a JSON array. Written using {@link JsonWriter#endArray} and read using {@link
* and read using {@link JsonReader#endArray}. * JsonReader#endArray}.
*/ */
END_ARRAY, END_ARRAY,
/** /**
* The opening of a JSON object. Written using {@link JsonWriter#beginObject} * The opening of a JSON object. Written using {@link JsonWriter#beginObject} and read using
* and read using {@link JsonReader#beginObject}. * {@link JsonReader#beginObject}.
*/ */
BEGIN_OBJECT, BEGIN_OBJECT,
/** /**
* The closing of a JSON object. Written using {@link JsonWriter#endObject} * The closing of a JSON object. Written using {@link JsonWriter#endObject} and read using {@link
* and read using {@link JsonReader#endObject}. * JsonReader#endObject}.
*/ */
END_OBJECT, END_OBJECT,
/** /**
* A JSON property name. Within objects, tokens alternate between names and * A JSON property name. Within objects, tokens alternate between names and their values. Written
* their values. Written using {@link JsonWriter#name} and read using {@link * using {@link JsonWriter#name} and read using {@link JsonReader#nextName}
* JsonReader#nextName}
*/ */
NAME, NAME,
/** /** A JSON string. */
* A JSON string.
*/
STRING, STRING,
/** /**
* A JSON number represented in this API by a Java {@code double}, {@code * A JSON number represented in this API by a Java {@code double}, {@code long}, or {@code int}.
* long}, or {@code int}.
*/ */
NUMBER, NUMBER,
/** /** A JSON {@code true} or {@code false}. */
* A JSON {@code true} or {@code false}.
*/
BOOLEAN, BOOLEAN,
/** /** A JSON {@code null}. */
* A JSON {@code null}.
*/
NULL, NULL,
/** /**
* The end of the JSON stream. This sentinel value is returned by {@link * The end of the JSON stream. This sentinel value is returned by {@link JsonReader#peek()} to
* JsonReader#peek()} to signal that the JSON-encoded value has no more * signal that the JSON-encoded value has no more tokens.
* tokens.
*/ */
END_DOCUMENT END_DOCUMENT
} }

View File

@ -42,43 +42,46 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Writes a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>) * Writes a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>) encoded value to a
* encoded value to a stream, one token at a time. The stream includes both * stream, one token at a time. The stream includes both literal values (strings, numbers, booleans
* literal values (strings, numbers, booleans and nulls) as well as the begin * and nulls) as well as the begin and end delimiters of objects and arrays.
* and end delimiters of objects and arrays.
* *
* <h2>Encoding JSON</h2> * <h2>Encoding JSON</h2>
* To encode your data as JSON, create a new {@code JsonWriter}. Call methods *
* on the writer as you walk the structure's contents, nesting arrays and objects * To encode your data as JSON, create a new {@code JsonWriter}. Call methods on the writer as you
* as necessary: * walk the structure's contents, nesting arrays and objects as necessary:
*
* <ul> * <ul>
* <li>To write <strong>arrays</strong>, first call {@link #beginArray()}. * <li>To write <strong>arrays</strong>, first call {@link #beginArray()}. Write each of the
* Write each of the array's elements with the appropriate {@link #value} * array's elements with the appropriate {@link #value} methods or by nesting other arrays and
* methods or by nesting other arrays and objects. Finally close the array * objects. Finally close the array using {@link #endArray()}.
* using {@link #endArray()}. * <li>To write <strong>objects</strong>, first call {@link #beginObject()}. Write each of the
* <li>To write <strong>objects</strong>, first call {@link #beginObject()}. * object's properties by alternating calls to {@link #name} with the property's value. Write
* Write each of the object's properties by alternating calls to * property values with the appropriate {@link #value} method or by nesting other objects or
* {@link #name} with the property's value. Write property values with the * arrays. Finally close the object using {@link #endObject()}.
* appropriate {@link #value} method or by nesting other objects or arrays.
* Finally close the object using {@link #endObject()}.
* </ul> * </ul>
* *
* <h2>Configuration</h2> * <h2>Configuration</h2>
*
* The behavior of this writer can be customized with the following methods: * The behavior of this writer can be customized with the following methods:
*
* <ul> * <ul>
* <li>{@link #setFormattingStyle(FormattingStyle)}, the default is {@link FormattingStyle#COMPACT} * <li>{@link #setFormattingStyle(FormattingStyle)}, the default is {@link
* <li>{@link #setHtmlSafe(boolean)}, by default HTML characters are not escaped * FormattingStyle#COMPACT}
* in the JSON output * <li>{@link #setHtmlSafe(boolean)}, by default HTML characters are not escaped in the JSON
* output
* <li>{@link #setStrictness(Strictness)}, the default is {@link Strictness#LEGACY_STRICT} * <li>{@link #setStrictness(Strictness)}, the default is {@link Strictness#LEGACY_STRICT}
* <li>{@link #setSerializeNulls(boolean)}, by default {@code null} is serialized * <li>{@link #setSerializeNulls(boolean)}, by default {@code null} is serialized
* </ul> * </ul>
* *
* The default configuration of {@code JsonWriter} instances used internally by * The default configuration of {@code JsonWriter} instances used internally by the {@link Gson}
* the {@link Gson} class differs, and can be adjusted with the various * class differs, and can be adjusted with the various {@link GsonBuilder} methods.
* {@link GsonBuilder} methods.
* *
* <h2>Example</h2> * <h2>Example</h2>
* Suppose we'd like to encode a stream of messages such as the following: <pre> {@code *
* Suppose we'd like to encode a stream of messages such as the following:
*
* <pre>{@code
* [ * [
* { * {
* "id": 912345678901, * "id": 912345678901,
@ -98,8 +101,12 @@ import java.util.regex.Pattern;
* "followers_count": 2 * "followers_count": 2
* } * }
* } * }
* ]}</pre> * ]
* This code encodes the above structure: <pre> {@code * }</pre>
*
* This code encodes the above structure:
*
* <pre>{@code
* public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException { * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
* JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8")); * JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
* writer.setIndent(" "); * writer.setIndent(" ");
@ -143,11 +150,12 @@ import java.util.regex.Pattern;
* writer.value(value); * writer.value(value);
* } * }
* writer.endArray(); * writer.endArray();
* }}</pre> * }
* }</pre>
* *
* <p>Each {@code JsonWriter} may be used to write a single JSON stream. * <p>Each {@code JsonWriter} may be used to write a single JSON stream. Instances of this class are
* Instances of this class are not thread safe. Calls that would result in a * not thread safe. Calls that would result in a malformed JSON string will fail with an {@link
* malformed JSON string will fail with an {@link IllegalStateException}. * IllegalStateException}.
* *
* @author Jesse Wilson * @author Jesse Wilson
* @since 1.6 * @since 1.6
@ -155,7 +163,8 @@ import java.util.regex.Pattern;
public class JsonWriter implements Closeable, Flushable { public class JsonWriter implements Closeable, Flushable {
// Syntax as defined by https://datatracker.ietf.org/doc/html/rfc8259#section-6 // Syntax as defined by https://datatracker.ietf.org/doc/html/rfc8259#section-6
private static final Pattern VALID_JSON_NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?"); private static final Pattern VALID_JSON_NUMBER_PATTERN =
Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?");
/* /*
* From RFC 8259, "All Unicode characters may be placed within the * From RFC 8259, "All Unicode characters may be placed within the
@ -169,6 +178,7 @@ public class JsonWriter implements Closeable, Flushable {
*/ */
private static final String[] REPLACEMENT_CHARS; private static final String[] REPLACEMENT_CHARS;
private static final String[] HTML_SAFE_REPLACEMENT_CHARS; private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
static { static {
REPLACEMENT_CHARS = new String[128]; REPLACEMENT_CHARS = new String[128];
for (int i = 0; i <= 0x1f; i++) { for (int i = 0; i <= 0x1f; i++) {
@ -194,6 +204,7 @@ public class JsonWriter implements Closeable, Flushable {
private int[] stack = new int[32]; private int[] stack = new int[32];
private int stackSize = 0; private int stackSize = 0;
{ {
push(EMPTY_DOCUMENT); push(EMPTY_DOCUMENT);
} }
@ -214,9 +225,9 @@ public class JsonWriter implements Closeable, Flushable {
private boolean serializeNulls = true; private boolean serializeNulls = true;
/** /**
* Creates a new instance that writes a JSON-encoded stream to {@code out}. * Creates a new instance that writes a JSON-encoded stream to {@code out}. For best performance,
* For best performance, ensure {@link Writer} is buffered; wrapping in * ensure {@link Writer} is buffered; wrapping in {@link java.io.BufferedWriter BufferedWriter} if
* {@link java.io.BufferedWriter BufferedWriter} if necessary. * necessary.
*/ */
public JsonWriter(Writer out) { public JsonWriter(Writer out) {
this.out = Objects.requireNonNull(out, "out == null"); this.out = Objects.requireNonNull(out, "out == null");
@ -224,16 +235,14 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Sets the indentation string to be repeated for each level of indentation * Sets the indentation string to be repeated for each level of indentation in the encoded
* in the encoded document. If {@code indent.isEmpty()} the encoded document * document. If {@code indent.isEmpty()} the encoded document will be compact. Otherwise the
* will be compact. Otherwise the encoded document will be more * encoded document will be more human-readable.
* human-readable.
* *
* <p>This is a convenience method which overwrites any previously * <p>This is a convenience method which overwrites any previously {@linkplain
* {@linkplain #setFormattingStyle(FormattingStyle) set formatting style} with * #setFormattingStyle(FormattingStyle) set formatting style} with either {@link
* either {@link FormattingStyle#COMPACT} if the given indent string is * FormattingStyle#COMPACT} if the given indent string is empty, or {@link FormattingStyle#PRETTY}
* empty, or {@link FormattingStyle#PRETTY} with the given indent if * with the given indent if not empty.
* not empty.
* *
* @param indent a string containing only whitespace. * @param indent a string containing only whitespace.
*/ */
@ -248,9 +257,8 @@ public class JsonWriter implements Closeable, Flushable {
/** /**
* Sets the formatting style to be used in the encoded document. * Sets the formatting style to be used in the encoded document.
* *
* <p>The formatting style specifies for example the indentation string to be * <p>The formatting style specifies for example the indentation string to be repeated for each
* repeated for each level of indentation, or the newline style, to accommodate * level of indentation, or the newline style, to accommodate various OS styles.
* various OS styles.</p>
* *
* @param formattingStyle the formatting style to use, must not be {@code null}. * @param formattingStyle the formatting style to use, must not be {@code null}.
* @since $next-version$ * @since $next-version$
@ -270,8 +278,8 @@ public class JsonWriter implements Closeable, Flushable {
this.formattedColon = ":"; this.formattedColon = ":";
} }
this.usesEmptyNewlineAndIndent = this.formattingStyle.getNewline().isEmpty() this.usesEmptyNewlineAndIndent =
&& this.formattingStyle.getIndent().isEmpty(); this.formattingStyle.getNewline().isEmpty() && this.formattingStyle.getIndent().isEmpty();
} }
/** /**
@ -287,17 +295,20 @@ public class JsonWriter implements Closeable, Flushable {
/** /**
* Sets the strictness of this writer. * Sets the strictness of this writer.
* *
* @deprecated Please use {@link #setStrictness(Strictness)} instead. * @deprecated Please use {@link #setStrictness(Strictness)} instead. {@code
* {@code JsonWriter.setLenient(true)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LENIENT)} * JsonWriter.setLenient(true)} should be replaced by {@code
* and {@code JsonWriter.setLenient(false)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LEGACY_STRICT)}.<br> * JsonWriter.setStrictness(Strictness.LENIENT)} and {@code JsonWriter.setLenient(false)}
* However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead. * should be replaced by {@code JsonWriter.setStrictness(Strictness.LEGACY_STRICT)}.<br>
* * However, if you used {@code setLenient(false)} before, you might prefer {@link
* @param lenient whether this writer should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}. * Strictness#STRICT} now instead.
* If false, the strictness is set to {@link Strictness#LEGACY_STRICT}. * @param lenient whether this writer should be lenient. If true, the strictness is set to {@link
* Strictness#LENIENT}. If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
* @see #setStrictness(Strictness) * @see #setStrictness(Strictness)
*/ */
@Deprecated @Deprecated
@SuppressWarnings("InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new Strictness.STRICT @SuppressWarnings(
"InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes
// aware of new Strictness.STRICT
public final void setLenient(boolean lenient) { public final void setLenient(boolean lenient) {
setStrictness(lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT); setStrictness(lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT);
} }
@ -313,19 +324,17 @@ public class JsonWriter implements Closeable, Flushable {
/** /**
* Configures how strict this writer is with regard to the syntax rules specified in <a * Configures how strict this writer is with regard to the syntax rules specified in <a
* href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>. By default, {@link Strictness#LEGACY_STRICT} is used. * href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>. By default, {@link
* Strictness#LEGACY_STRICT} is used.
* *
* <dl> * <dl>
* <dt>{@link Strictness#STRICT} &amp; {@link Strictness#LEGACY_STRICT}</dt> * <dt>{@link Strictness#STRICT} &amp; {@link Strictness#LEGACY_STRICT}
* <dd> * <dd>The behavior of these is currently identical. In these strictness modes, the writer only
* The behavior of these is currently identical. In these strictness modes, the writer only writes JSON * writes JSON in accordance with RFC 8259.
* in accordance with RFC 8259. * <dt>{@link Strictness#LENIENT}
* </dd> * <dd>This mode relaxes the behavior of the writer to allow the writing of {@link
* <dt>{@link Strictness#LENIENT}</dt> * Double#isNaN() NaNs} and {@link Double#isInfinite() infinities}. It also allows writing
* <dd> * multiple top level values.
* This mode relaxes the behavior of the writer to allow the writing of {@link Double#isNaN() NaNs}
* and {@link Double#isInfinite() infinities}. It also allows writing multiple top level values.
* </dd>
* </dl> * </dl>
* *
* @param strictness the new strictness of this writer. May not be {@code null}. * @param strictness the new strictness of this writer. May not be {@code null}.
@ -346,43 +355,41 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Configures this writer to emit JSON that's safe for direct inclusion in HTML * Configures this writer to emit JSON that's safe for direct inclusion in HTML and XML documents.
* and XML documents. This escapes the HTML characters {@code <}, {@code >}, * This escapes the HTML characters {@code <}, {@code >}, {@code &} and {@code =} before writing
* {@code &} and {@code =} before writing them to the stream. Without this * them to the stream. Without this setting, your XML/HTML encoder should replace these characters
* setting, your XML/HTML encoder should replace these characters with the * with the corresponding escape sequences.
* corresponding escape sequences.
*/ */
public final void setHtmlSafe(boolean htmlSafe) { public final void setHtmlSafe(boolean htmlSafe) {
this.htmlSafe = htmlSafe; this.htmlSafe = htmlSafe;
} }
/** /**
* Returns true if this writer writes JSON that's safe for inclusion in HTML * Returns true if this writer writes JSON that's safe for inclusion in HTML and XML documents.
* and XML documents.
*/ */
public final boolean isHtmlSafe() { public final boolean isHtmlSafe() {
return htmlSafe; return htmlSafe;
} }
/** /**
* Sets whether object members are serialized when their value is null. * Sets whether object members are serialized when their value is null. This has no impact on
* This has no impact on array elements. The default is true. * array elements. The default is true.
*/ */
public final void setSerializeNulls(boolean serializeNulls) { public final void setSerializeNulls(boolean serializeNulls) {
this.serializeNulls = serializeNulls; this.serializeNulls = serializeNulls;
} }
/** /**
* Returns true if object members are serialized when their value is null. * Returns true if object members are serialized when their value is null. This has no impact on
* This has no impact on array elements. The default is true. * array elements. The default is true.
*/ */
public final boolean getSerializeNulls() { public final boolean getSerializeNulls() {
return serializeNulls; return serializeNulls;
} }
/** /**
* Begins encoding a new array. Each call to this method must be paired with * Begins encoding a new array. Each call to this method must be paired with a call to {@link
* a call to {@link #endArray}. * #endArray}.
* *
* @return this writer. * @return this writer.
*/ */
@ -403,8 +410,8 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Begins encoding a new object. Each call to this method must be paired * Begins encoding a new object. Each call to this method must be paired with a call to {@link
* with a call to {@link #endObject}. * #endObject}.
* *
* @return this writer. * @return this writer.
*/ */
@ -424,10 +431,7 @@ public class JsonWriter implements Closeable, Flushable {
return close(EMPTY_OBJECT, NONEMPTY_OBJECT, '}'); return close(EMPTY_OBJECT, NONEMPTY_OBJECT, '}');
} }
/** /** Enters a new scope by appending any necessary whitespace and the given bracket. */
* Enters a new scope by appending any necessary whitespace and the given
* bracket.
*/
@CanIgnoreReturnValue @CanIgnoreReturnValue
private JsonWriter open(int empty, char openBracket) throws IOException { private JsonWriter open(int empty, char openBracket) throws IOException {
beforeValue(); beforeValue();
@ -436,13 +440,9 @@ public class JsonWriter implements Closeable, Flushable {
return this; return this;
} }
/** /** Closes the current scope by appending any necessary whitespace and the given bracket. */
* Closes the current scope by appending any necessary whitespace and the
* given bracket.
*/
@CanIgnoreReturnValue @CanIgnoreReturnValue
private JsonWriter close(int empty, int nonempty, char closeBracket) private JsonWriter close(int empty, int nonempty, char closeBracket) throws IOException {
throws IOException {
int context = peek(); int context = peek();
if (context != nonempty && context != empty) { if (context != nonempty && context != empty) {
throw new IllegalStateException("Nesting problem."); throw new IllegalStateException("Nesting problem.");
@ -466,9 +466,7 @@ public class JsonWriter implements Closeable, Flushable {
stack[stackSize++] = newTop; stack[stackSize++] = newTop;
} }
/** /** Returns the value on the top of the stack. */
* Returns the value on the top of the stack.
*/
private int peek() { private int peek() {
if (stackSize == 0) { if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed."); throw new IllegalStateException("JsonWriter is closed.");
@ -476,9 +474,7 @@ public class JsonWriter implements Closeable, Flushable {
return stack[stackSize - 1]; return stack[stackSize - 1];
} }
/** /** Replace the value on the top of the stack with the given value. */
* Replace the value on the top of the stack with the given value.
*/
private void replaceTop(int topOfStack) { private void replaceTop(int topOfStack) {
stack[stackSize - 1] = topOfStack; stack[stackSize - 1] = topOfStack;
} }
@ -529,14 +525,13 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Writes {@code value} directly to the writer without quoting or * Writes {@code value} directly to the writer without quoting or escaping. This might not be
* escaping. This might not be supported by all implementations, if * supported by all implementations, if not supported an {@code UnsupportedOperationException} is
* not supported an {@code UnsupportedOperationException} is thrown. * thrown.
* *
* @param value the literal string value, or null to encode a null literal. * @param value the literal string value, or null to encode a null literal.
* @return this writer. * @return this writer.
* @throws UnsupportedOperationException if this writer does not support * @throws UnsupportedOperationException if this writer does not support writing raw JSON values.
* writing raw JSON values.
* @since 2.4 * @since 2.4
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
@ -603,9 +598,8 @@ public class JsonWriter implements Closeable, Flushable {
/** /**
* Encodes {@code value}. * Encodes {@code value}.
* *
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, * @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, also {@link
* also {@link Float#isNaN() NaN} or {@link Float#isInfinite() * Float#isNaN() NaN} or {@link Float#isInfinite() infinity}.
* infinity}.
* @return this writer. * @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
* #setStrictness(Strictness) lenient}. * #setStrictness(Strictness) lenient}.
@ -625,11 +619,11 @@ public class JsonWriter implements Closeable, Flushable {
/** /**
* Encodes {@code value}. * Encodes {@code value}.
* *
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, * @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, also {@link
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}. * Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
* @return this writer. * @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
* not {@link #setStrictness(Strictness) lenient}. * #setStrictness(Strictness) lenient}.
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public JsonWriter value(double value) throws IOException { public JsonWriter value(double value) throws IOException {
@ -656,26 +650,34 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Returns whether the {@code toString()} of {@code c} can be trusted to return * Returns whether the {@code toString()} of {@code c} can be trusted to return a valid JSON
* a valid JSON number. * number.
*/ */
private static boolean isTrustedNumberType(Class<? extends Number> c) { private static boolean isTrustedNumberType(Class<? extends Number> c) {
// Note: Don't consider LazilyParsedNumber trusted because it could contain // Note: Don't consider LazilyParsedNumber trusted because it could contain
// an arbitrary malformed string // an arbitrary malformed string
return c == Integer.class || c == Long.class || c == Double.class || c == Float.class || c == Byte.class || c == Short.class return c == Integer.class
|| c == BigDecimal.class || c == BigInteger.class || c == AtomicInteger.class || c == AtomicLong.class; || c == Long.class
|| c == Double.class
|| c == Float.class
|| c == Byte.class
|| c == Short.class
|| c == BigDecimal.class
|| c == BigInteger.class
|| c == AtomicInteger.class
|| c == AtomicLong.class;
} }
/** /**
* Encodes {@code value}. The value is written by directly writing the {@link Number#toString()} * Encodes {@code value}. The value is written by directly writing the {@link Number#toString()}
* result to JSON. Implementations must make sure that the result represents a valid JSON number. * result to JSON. Implementations must make sure that the result represents a valid JSON number.
* *
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, * @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, also {@link
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}. * Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
* @return this writer. * @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is * @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
* not {@link #setStrictness(Strictness) lenient}; or if the {@code toString()} result is not a * #setStrictness(Strictness) lenient}; or if the {@code toString()} result is not a valid
* valid JSON number. * JSON number.
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public JsonWriter value(Number value) throws IOException { public JsonWriter value(Number value) throws IOException {
@ -692,8 +694,10 @@ public class JsonWriter implements Closeable, Flushable {
} else { } else {
Class<? extends Number> numberClass = value.getClass(); Class<? extends Number> numberClass = value.getClass();
// Validate that string is valid before writing it directly to JSON output // Validate that string is valid before writing it directly to JSON output
if (!isTrustedNumberType(numberClass) && !VALID_JSON_NUMBER_PATTERN.matcher(string).matches()) { if (!isTrustedNumberType(numberClass)
throw new IllegalArgumentException("String created by " + numberClass + " is not a valid JSON number: " + string); && !VALID_JSON_NUMBER_PATTERN.matcher(string).matches()) {
throw new IllegalArgumentException(
"String created by " + numberClass + " is not a valid JSON number: " + string);
} }
} }
@ -703,10 +707,10 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Ensures all buffered data is written to the underlying {@link Writer} * Ensures all buffered data is written to the underlying {@link Writer} and flushes that writer.
* and flushes that writer.
*/ */
@Override public void flush() throws IOException { @Override
public void flush() throws IOException {
if (stackSize == 0) { if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed."); throw new IllegalStateException("JsonWriter is closed.");
} }
@ -718,7 +722,8 @@ public class JsonWriter implements Closeable, Flushable {
* *
* @throws IOException if the JSON document is incomplete. * @throws IOException if the JSON document is incomplete.
*/ */
@Override public void close() throws IOException { @Override
public void close() throws IOException {
out.close(); out.close();
int size = stackSize; int size = stackSize;
@ -772,8 +777,8 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Inserts any necessary separators and whitespace before a name. Also * Inserts any necessary separators and whitespace before a name. Also adjusts the stack to expect
* adjusts the stack to expect the name's value. * the name's value.
*/ */
private void beforeName() throws IOException { private void beforeName() throws IOException {
int context = peek(); int context = peek();
@ -787,17 +792,15 @@ public class JsonWriter implements Closeable, Flushable {
} }
/** /**
* Inserts any necessary separators and whitespace before a literal value, * Inserts any necessary separators and whitespace before a literal value, inline array, or inline
* inline array, or inline object. Also adjusts the stack to expect either a * object. Also adjusts the stack to expect either a closing bracket or another element.
* closing bracket or another element.
*/ */
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
private void beforeValue() throws IOException { private void beforeValue() throws IOException {
switch (peek()) { switch (peek()) {
case NONEMPTY_DOCUMENT: case NONEMPTY_DOCUMENT:
if (strictness != Strictness.LENIENT) { if (strictness != Strictness.LENIENT) {
throw new IllegalStateException( throw new IllegalStateException("JSON must have only one top-level value.");
"JSON must have only one top-level value.");
} }
// fall-through // fall-through
case EMPTY_DOCUMENT: // first in document case EMPTY_DOCUMENT: // first in document

View File

@ -20,8 +20,8 @@ import com.google.gson.Strictness;
import java.io.IOException; import java.io.IOException;
/** /**
* Thrown when a reader encounters malformed JSON. Some syntax errors can be * Thrown when a reader encounters malformed JSON. Some syntax errors can be ignored by using {@link
* ignored by using {@link Strictness#LENIENT} for {@link JsonReader#setStrictness(Strictness)}. * Strictness#LENIENT} for {@link JsonReader#setStrictness(Strictness)}.
*/ */
public final class MalformedJsonException extends IOException { public final class MalformedJsonException extends IOException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -14,7 +14,5 @@
* limitations under the License. * limitations under the License.
*/ */
/** /** This package provides classes for processing JSON in an efficient streaming way. */
* This package provides classes for processing JSON in an efficient streaming way.
*/
package com.google.gson.stream; package com.google.gson.stream;

View File

@ -16,6 +16,7 @@
/** /**
* Defines the Gson serialization/deserialization API. * Defines the Gson serialization/deserialization API.
*
* @since 2.8.6 * @since 2.8.6
*/ */
module com.google.gson { module com.google.gson {

View File

@ -29,12 +29,11 @@ import org.junit.Test;
*/ */
public final class CommentsTest { public final class CommentsTest {
/** /** Test for issue 212. */
* Test for issue 212.
*/
@Test @Test
public void testParseComments() { public void testParseComments() {
String json = "[\n" String json =
"[\n"
+ " // this is a comment\n" + " // this is a comment\n"
+ " \"a\",\n" + " \"a\",\n"
+ " /* this is another comment */\n" + " /* this is another comment */\n"

View File

@ -25,8 +25,8 @@ import java.util.Map;
import org.junit.Test; import org.junit.Test;
/** /**
* Unit test for the default JSON map serialization object located in the * Unit test for the default JSON map serialization object located in the {@link
* {@link DefaultTypeAdapters} class. * DefaultTypeAdapters} class.
* *
* @author Joel Leitch * @author Joel Leitch
*/ */
@ -44,7 +44,7 @@ public class DefaultMapJsonSerializerTest {
@Test @Test
public void testEmptyMapSerialization() { public void testEmptyMapSerialization() {
Type mapType = new TypeToken<Map<String, String>>() { }.getType(); Type mapType = new TypeToken<Map<String, String>>() {}.getType();
Map<String, String> emptyMap = new HashMap<>(); Map<String, String> emptyMap = new HashMap<>();
JsonElement element = gson.toJsonTree(emptyMap, mapType); JsonElement element = gson.toJsonTree(emptyMap, mapType);
@ -55,7 +55,7 @@ public class DefaultMapJsonSerializerTest {
@Test @Test
public void testNonEmptyMapSerialization() { public void testNonEmptyMapSerialization() {
Type mapType = new TypeToken<Map<String, String>>() { }.getType(); Type mapType = new TypeToken<Map<String, String>>() {}.getType();
Map<String, String> myMap = new HashMap<>(); Map<String, String> myMap = new HashMap<>();
String key = "key1"; String key = "key1";
myMap.put(key, "value1"); myMap.put(key, "value1");

View File

@ -78,16 +78,15 @@ public class ExposeAnnotationExclusionStrategyTest {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class MockObject { private static class MockObject {
@Expose @Expose public final int exposedField = 0;
public final int exposedField = 0;
@Expose(serialize=true, deserialize=true) @Expose(serialize = true, deserialize = true)
public final int explicitlyExposedField = 0; public final int explicitlyExposedField = 0;
@Expose(serialize=false, deserialize=false) @Expose(serialize = false, deserialize = false)
public final int explicitlyHiddenField = 0; public final int explicitlyHiddenField = 0;
@Expose(serialize=true, deserialize=false) @Expose(serialize = true, deserialize = false)
public final int explicitlyDifferentModeField = 0; public final int explicitlyDifferentModeField = 0;
public final int hiddenField = 0; public final int hiddenField = 0;

Some files were not shown because too many files have changed in this diff Show More