parent
cb6643f148
commit
2c94c757a6
@ -15,21 +15,22 @@
|
|||||||
*/
|
*/
|
||||||
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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
* @Intercept(postDeserialize=UserValidator.class)
|
* @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();
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,10 @@ 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;
|
||||||
@ -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
|
||||||
|
* simple name will be used.
|
||||||
|
*
|
||||||
* <pre>{@code
|
* <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,13 +140,17 @@ 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
|
||||||
|
* explicitly.
|
||||||
|
*
|
||||||
* <pre>{@code
|
* <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));
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,14 +70,17 @@ 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);
|
||||||
}
|
}
|
||||||
@ -86,8 +91,11 @@ public final class InterceptorTest {
|
|||||||
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.");
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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<?> excludedThisClass;
|
* private final Class<?> 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 @interface FooAnnotation {
|
* public @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 {
|
||||||
|
@ -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<List<String>>() {}.getType();
|
* Type listParameterizedType = new TypeToken<List<String>>() {}.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>
|
||||||
|
@ -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" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> SomeFieldName</li>
|
* <li>someFieldName ---> SomeFieldName
|
||||||
* <li>_someFieldName ---> _SomeFieldName</li>
|
* <li>_someFieldName ---> _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" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> Some Field Name</li>
|
* <li>someFieldName ---> Some Field Name
|
||||||
* <li>_someFieldName ---> _Some Field Name</li>
|
* <li>_someFieldName ---> _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" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> SOME_FIELD_NAME</li>
|
* <li>someFieldName ---> SOME_FIELD_NAME
|
||||||
* <li>_someFieldName ---> _SOME_FIELD_NAME</li>
|
* <li>_someFieldName ---> _SOME_FIELD_NAME
|
||||||
* <li>aStringField ---> A_STRING_FIELD</li>
|
* <li>aStringField ---> A_STRING_FIELD
|
||||||
* <li>aURL ---> A_U_R_L</li>
|
* <li>aURL ---> 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" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> some_field_name</li>
|
* <li>someFieldName ---> some_field_name
|
||||||
* <li>_someFieldName ---> _some_field_name</li>
|
* <li>_someFieldName ---> _some_field_name
|
||||||
* <li>aStringField ---> a_string_field</li>
|
* <li>aStringField ---> a_string_field
|
||||||
* <li>aURL ---> a_u_r_l</li>
|
* <li>aURL ---> 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" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> some-field-name</li>
|
* <li>someFieldName ---> some-field-name
|
||||||
* <li>_someFieldName ---> _some-field-name</li>
|
* <li>_someFieldName ---> _some-field-name
|
||||||
* <li>aStringField ---> a-string-field</li>
|
* <li>aStringField ---> a-string-field
|
||||||
* <li>aURL ---> a-u-r-l</li>
|
* <li>aURL ---> 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" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> some.field.name</li>
|
* <li>someFieldName ---> some.field.name
|
||||||
* <li>_someFieldName ---> _some.field.name</li>
|
* <li>_someFieldName ---> _some.field.name
|
||||||
* <li>aStringField ---> a.string.field</li>
|
* <li>aStringField ---> a.string.field
|
||||||
* <li>aURL ---> a.u.r.l</li>
|
* <li>aURL ---> 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++) {
|
||||||
|
@ -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
|
||||||
|
@ -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
@ -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,20 +239,19 @@ 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())
|
||||||
@ -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,15 +275,15 @@ 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()
|
||||||
@ -291,10 +293,10 @@ 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;
|
||||||
|
@ -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<T> {
|
* public class Id<T> {
|
||||||
@ -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<Id> {
|
* class IdInstanceCreator implements InstanceCreator<Id> {
|
||||||
@ -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.
|
||||||
|
@ -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() {
|
||||||
|
@ -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.
|
||||||
|
@ -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<T> {
|
* public class Id<T> {
|
||||||
@ -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<Id> {
|
* class IdDeserializer implements JsonDeserializer<Id> {
|
||||||
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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;
|
||||||
|
@ -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() {
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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<T> {
|
* public class Id<T> {
|
||||||
@ -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<Id> {
|
* class IdSerializer implements JsonSerializer<Id> {
|
||||||
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 §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.
|
* §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);
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
* @Expose private String firstName;
|
* @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;
|
||||||
|
@ -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>
|
||||||
* @JsonAdapter(UserJsonAdapter.class)
|
* @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 {
|
||||||
* @JsonAdapter(UserJsonAdapter.class)
|
* @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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
* @SerializedName("name") String a;
|
* @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
|
||||||
*
|
*
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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,10 +67,10 @@ 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;
|
||||||
@ -87,9 +83,8 @@ public final class $Gson$Types {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
@ -102,9 +97,8 @@ public final class $Gson$Types {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
@ -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,
|
||||||
|
// use it instead
|
||||||
Type[] bounds = ((WildcardType) context).getUpperBounds();
|
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) {
|
||||||
/*
|
/*
|
||||||
@ -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
|
||||||
|
public Type[] getUpperBounds() {
|
||||||
return new Type[] {upperBound};
|
return new Type[] {upperBound};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Type[] getLowerBounds() {
|
@Override
|
||||||
|
public Type[] getLowerBounds() {
|
||||||
return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY;
|
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) {
|
||||||
|
@ -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,12 +177,15 @@ 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) {
|
||||||
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,7 +86,8 @@ 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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,15 +120,14 @@ 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;
|
||||||
@ -131,14 +135,18 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
|
|||||||
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;
|
||||||
|
@ -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
|
||||||
|
public void close() throws IOException {
|
||||||
stack = new Object[] {SENTINEL_CLOSED};
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -41,46 +41,56 @@ 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
|
||||||
|
* this case the arrays elements are map entries:
|
||||||
|
*
|
||||||
* <pre>{@code
|
* <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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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();
|
||||||
|
@ -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 + "]";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
@ -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(
|
||||||
|
"Mismatching time zone indicator: "
|
||||||
|
+ timezoneId
|
||||||
|
+ " given, resolves to "
|
||||||
+ timezone.getID());
|
+ 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);
|
||||||
@ -292,7 +299,8 @@ public class ISO8601Utils
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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() {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
@ -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,8 +1772,10 @@ 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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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} & {@link Strictness#LEGACY_STRICT}</dt>
|
* <dt>{@link Strictness#STRICT} & {@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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -78,8 +78,7 @@ 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;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user