Bye
ci/woodpecker/push/woodpecker Pipeline failed Details

This commit is contained in:
Johannes Frohnmeyer 2022-11-24 18:00:06 +01:00
parent 79ae63f02a
commit 237f6a7153
Signed by: Johannes
GPG Key ID: E76429612C2929F4
33 changed files with 0 additions and 4083 deletions

View File

@ -1,6 +0,0 @@
# extras
This Maven module contains the source code for supplementary Gson features which
are not included by default.
The artifacts created by this module are currently not deployed to Maven Central.

View File

@ -1,95 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.11.0-SNAPSHOT</version>
</parent>
<artifactId>gson-extras</artifactId>
<inceptionYear>2008</inceptionYear>
<name>Gson Extras</name>
<description>Google Gson grab bag of utilities, type adapters, etc.</description>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<organization>
<name>Google, Inc.</name>
<url>https://www.google.com</url>
</organization>
<dependencies>
<dependency>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- Currently not deployed -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>io.gitlab.jfronny.gson</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<developers>
<developer>
<name>Inderjeet Singh</name>
</developer>
<developer>
<name>Joel Leitch</name>
<organization>Google Inc.</organization>
</developer>
<developer>
<name>Jesse Wilson</name>
<organization>Square Inc.</organization>
</developer>
</developers>
</project>

View File

@ -1,54 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.extras.examples.rawcollections;
import java.util.ArrayList;
import java.util.Collection;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
public class RawCollectionsExample {
static class Event {
private String name;
private String source;
private Event(String name, String source) {
this.name = name;
this.source = source;
}
@Override
public String toString() {
return String.format("(name=%s, source=%s)", name, source);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static void main(String[] args) {
Gson gson = new Gson();
Collection collection = new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS", "guest"));
String json = gson.toJson(collection);
System.out.println("Using Gson.toJson() on a raw collection: " + json);
JsonArray array = JsonParser.parseString(json).getAsJsonArray();
String message = gson.fromJson(array.get(0), String.class);
int number = gson.fromJson(array.get(1), int.class);
Event event = gson.fromJson(array.get(2), Event.class);
System.out.printf("Using Gson.fromJson() to get: %s, %d, %s", message, number, event);
}
}

View File

@ -1,312 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.graph;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonElement;
import com.google.gson.ReflectionAccessFilter;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.ObjectConstructor;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
/**
* Writes a graph of objects as a list of named nodes.
*/
// TODO: proper documentation
public final class GraphAdapterBuilder {
private final Map<Type, InstanceCreator<?>> instanceCreators;
private final ConstructorConstructor constructorConstructor;
public GraphAdapterBuilder() {
this.instanceCreators = new HashMap<>();
this.constructorConstructor = new ConstructorConstructor(instanceCreators, true, Collections.<ReflectionAccessFilter>emptyList());
}
public GraphAdapterBuilder addType(Type type) {
final ObjectConstructor<?> objectConstructor = constructorConstructor.get(TypeToken.get(type));
InstanceCreator<Object> instanceCreator = new InstanceCreator<Object>() {
@Override
public Object createInstance(Type type) {
return objectConstructor.construct();
}
};
return addType(type, instanceCreator);
}
public GraphAdapterBuilder addType(Type type, InstanceCreator<?> instanceCreator) {
if (type == null || instanceCreator == null) {
throw new NullPointerException();
}
instanceCreators.put(type, instanceCreator);
return this;
}
public void registerOn(GsonBuilder gsonBuilder) {
Factory factory = new Factory(instanceCreators);
gsonBuilder.registerTypeAdapterFactory(factory);
for (Map.Entry<Type, InstanceCreator<?>> entry : instanceCreators.entrySet()) {
gsonBuilder.registerTypeAdapter(entry.getKey(), factory);
}
}
static class Factory implements TypeAdapterFactory, InstanceCreator<Object> {
private final Map<Type, InstanceCreator<?>> instanceCreators;
private final ThreadLocal<Graph> graphThreadLocal = new ThreadLocal<>();
Factory(Map<Type, InstanceCreator<?>> instanceCreators) {
this.instanceCreators = instanceCreators;
}
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!instanceCreators.containsKey(type.getType())) {
return null;
}
final TypeAdapter<T> typeAdapter = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
@Override public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
Graph graph = graphThreadLocal.get();
boolean writeEntireGraph = false;
/*
* We have one of two cases:
* 1. We've encountered the first known object in this graph. Write
* out the graph, starting with that object.
* 2. We've encountered another graph object in the course of #1.
* Just write out this object's name. We'll circle back to writing
* out the object's value as a part of #1.
*/
if (graph == null) {
writeEntireGraph = true;
graph = new Graph(new IdentityHashMap<Object, Element<?>>());
}
@SuppressWarnings("unchecked") // graph.map guarantees consistency between value and T
Element<T> element = (Element<T>) graph.map.get(value);
if (element == null) {
element = new Element<>(value, graph.nextName(), typeAdapter, null);
graph.map.put(value, element);
graph.queue.add(element);
}
if (writeEntireGraph) {
graphThreadLocal.set(graph);
try {
out.beginObject();
Element<?> current;
while ((current = graph.queue.poll()) != null) {
out.name(current.id);
current.write(out);
}
out.endObject();
} finally {
graphThreadLocal.remove();
}
} else {
out.value(element.id);
}
}
@Override public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
/*
* Again we have one of two cases:
* 1. We've encountered the first known object in this graph. Read
* the entire graph in as a map from names to their JsonElements.
* Then convert the first JsonElement to its Java object.
* 2. We've encountered another graph object in the course of #1.
* Read in its name, then deserialize its value from the
* JsonElement in our map. We need to do this lazily because we
* don't know which TypeAdapter to use until a value is
* encountered in the wild.
*/
String currentName = null;
Graph graph = graphThreadLocal.get();
boolean readEntireGraph = false;
if (graph == null) {
graph = new Graph(new HashMap<Object, Element<?>>());
readEntireGraph = true;
// read the entire tree into memory
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
if (currentName == null) {
currentName = name;
}
JsonElement element = elementAdapter.read(in);
graph.map.put(name, new Element<>(null, name, typeAdapter, element));
}
in.endObject();
} else {
currentName = in.nextString();
}
if (readEntireGraph) {
graphThreadLocal.set(graph);
}
try {
@SuppressWarnings("unchecked") // graph.map guarantees consistency between value and T
Element<T> element = (Element<T>) graph.map.get(currentName);
// now that we know the typeAdapter for this name, go from JsonElement to 'T'
if (element.value == null) {
element.typeAdapter = typeAdapter;
element.read(graph);
}
return element.value;
} finally {
if (readEntireGraph) {
graphThreadLocal.remove();
}
}
}
};
}
/**
* Hook for the graph adapter to get a reference to a deserialized value
* before that value is fully populated. This is useful to deserialize
* values that directly or indirectly reference themselves: we can hand
* out an instance before read() returns.
*
* <p>Gson should only ever call this method when we're expecting it to;
* that is only when we've called back into Gson to deserialize a tree.
*/
@Override
public Object createInstance(Type type) {
Graph graph = graphThreadLocal.get();
if (graph == null || graph.nextCreate == null) {
throw new IllegalStateException("Unexpected call to createInstance() for " + type);
}
InstanceCreator<?> creator = instanceCreators.get(type);
Object result = creator.createInstance(type);
graph.nextCreate.value = result;
graph.nextCreate = null;
return result;
}
}
static class Graph {
/**
* The graph elements. On serialization keys are objects (using an identity
* hash map) and on deserialization keys are the string names (using a
* standard hash map).
*/
private final Map<Object, Element<?>> map;
/**
* The queue of elements to write during serialization. Unused during
* deserialization.
*/
private final Queue<Element<?>> queue = new LinkedList<>();
/**
* The instance currently being deserialized. Used as a backdoor between
* the graph traversal (which needs to know instances) and instance creators
* which create them.
*/
private Element<Object> nextCreate;
private Graph(Map<Object, Element<?>> map) {
this.map = map;
}
/**
* Returns a unique name for an element to be inserted into the graph.
*/
public String nextName() {
return "0x" + Integer.toHexString(map.size() + 1);
}
}
/**
* An element of the graph during serialization or deserialization.
*/
static class Element<T> {
/**
* This element's name in the top level graph object.
*/
private final String id;
/**
* The value if known. During deserialization this is lazily populated.
*/
private T value;
/**
* This element's type adapter if known. During deserialization this is
* lazily populated.
*/
private TypeAdapter<T> typeAdapter;
/**
* The element to deserialize. Unused in serialization.
*/
private final JsonElement element;
Element(T value, String id, TypeAdapter<T> typeAdapter, JsonElement element) {
this.value = value;
this.id = id;
this.typeAdapter = typeAdapter;
this.element = element;
}
void write(JsonWriter out) throws IOException {
typeAdapter.write(out, value);
}
@SuppressWarnings("unchecked")
void read(Graph graph) throws IOException {
if (graph.nextCreate != null) {
throw new IllegalStateException("Unexpected recursive call to read() for " + id);
}
graph.nextCreate = (Element<Object>) this;
value = typeAdapter.fromJsonTree(element);
if (value == null) {
throw new IllegalStateException("non-null value deserialized to null: " + element);
}
}
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (C) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.interceptors;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Use this annotation to indicate various interceptors for class instances after
* they have been processed by Gson. For example, you can use it to validate an instance
* after it has been deserialized from Json.
* Here is an example of how this annotation is used:
* <p>Here is an example of how this annotation is used:
* <pre>
* &#64;Intercept(postDeserialize=UserValidator.class)
* public class User {
* String name;
* String password;
* String emailAddress;
* }
*
* public class UserValidator implements JsonPostDeserializer&lt;User&gt; {
* public void postDeserialize(User user) {
* // Do some checks on user
* if (user.name == null || user.password == null) {
* throw new JsonParseException("name and password are required fields.");
* }
* if (user.emailAddress == null) {
* emailAddress = "unknown"; // assign a default value.
* }
* }
* }
* </pre>
*
* @author Inderjeet Singh
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercept {
/**
* Specify the class that provides the methods that should be invoked after an instance
* has been deserialized.
*/
@SuppressWarnings("rawtypes")
public Class<? extends JsonPostDeserializer> postDeserialize();
}

View File

@ -1,49 +0,0 @@
package com.google.gson.interceptors;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
/**
* A type adapter factory that implements {@code @Intercept}.
*/
public final class InterceptorFactory implements TypeAdapterFactory {
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Intercept intercept = type.getRawType().getAnnotation(Intercept.class);
if (intercept == null) {
return null;
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new InterceptorAdapter<>(delegate, intercept);
}
static class InterceptorAdapter<T> extends TypeAdapter<T> {
private final TypeAdapter<T> delegate;
private final JsonPostDeserializer<T> postDeserializer;
@SuppressWarnings("unchecked") // ?
public InterceptorAdapter(TypeAdapter<T> delegate, Intercept intercept) {
try {
this.delegate = delegate;
this.postDeserializer = intercept.postDeserialize().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
@Override public T read(JsonReader in) throws IOException {
T result = delegate.read(in);
postDeserializer.postDeserialize(result);
return result;
}
}
}

View File

@ -1,33 +0,0 @@
/*
* Copyright (C) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.interceptors;
import com.google.gson.InstanceCreator;
/**
* This interface is implemented by a class that wishes to inspect or modify an object
* after it has been deserialized. You must define a no-args constructor or register an
* {@link InstanceCreator} for such a class.
*
* @author Inderjeet Singh
*/
public interface JsonPostDeserializer<T> {
/**
* This method is called by Gson after the object has been deserialized from Json.
*/
public void postDeserialize(T object);
}

View File

@ -1,76 +0,0 @@
/*
* Copyright (C) 2016 Gson Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.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.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class PostConstructAdapterFactory implements TypeAdapterFactory {
// copied from https://gist.github.com/swankjesse/20df26adaf639ed7fd160f145a0b661a
@Override
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 (Method m : t.getDeclaredMethods()) {
if (m.isAnnotationPresent(PostConstruct.class)) {
m.setAccessible(true);
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new PostConstructAdapter<>(delegate, m);
}
}
}
return null;
}
final static class PostConstructAdapter<T> extends TypeAdapter<T> {
private final TypeAdapter<T> delegate;
private final Method method;
public PostConstructAdapter(TypeAdapter<T> delegate, Method method) {
this.delegate = delegate;
this.method = method;
}
@Override public T read(JsonReader in) throws IOException {
T result = delegate.read(in);
if (result != null) {
try {
method.invoke(result);
} catch (IllegalAccessException e) {
throw new AssertionError();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) throw (RuntimeException) e.getCause();
throw new RuntimeException(e.getCause());
}
}
return result;
}
@Override public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
}
}

View File

@ -1,293 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.typeadapters;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Adapts values whose runtime type may differ from their declaration type. This
* is necessary when a field's type is not the same type that GSON should create
* when deserializing that field. For example, consider these types:
* <pre> {@code
* abstract class Shape {
* int x;
* int y;
* }
* class Circle extends Shape {
* int radius;
* }
* class Rectangle extends Shape {
* int width;
* int height;
* }
* class Diamond extends Shape {
* int width;
* int height;
* }
* class Drawing {
* Shape bottomShape;
* Shape topShape;
* }
* }</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
* {
* "bottomShape": {
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* 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": {
* "type": "Diamond",
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "type": "Circle",
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* Both the type field name ({@code "type"}) and the type labels ({@code
* "Rectangle"}) are configurable.
*
* <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
* field name, {@code "type"} will be used. <pre> {@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory
* = RuntimeTypeAdapterFactory.of(Shape.class, "type");
* }</pre>
* Next register all of your subtypes. Every subtype must be explicitly
* registered. This protects your application from injection attacks. If you
* don't supply an explicit type label, the type's simple name will be used.
* <pre> {@code
* shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
* shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
* shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
* }</pre>
* Finally, register the type adapter factory in your application's GSON builder:
* <pre> {@code
* Gson gson = new GsonBuilder()
* .registerTypeAdapterFactory(shapeAdapterFactory)
* .create();
* }</pre>
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
* RuntimeTypeAdapterFactory<Shape> shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
* .registerSubtype(Rectangle.class)
* .registerSubtype(Circle.class)
* .registerSubtype(Diamond.class);
* }</pre>
*
* <h2>Serialization and deserialization</h2>
* In order to serialize and deserialize a polymorphic object,
* you must specify the base type explicitly.
* <pre> {@code
* Diamond diamond = new Diamond();
* String json = gson.toJson(diamond, Shape.class);
* }</pre>
* And then:
* <pre> {@code
* Shape shape = gson.fromJson(json, Shape.class);
* }</pre>
*/
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType;
private final String typeFieldName;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
private final boolean maintainType;
private boolean recognizeSubtypes;
private RuntimeTypeAdapterFactory(
Class<?> baseType, String typeFieldName, boolean maintainType) {
if (typeFieldName == null || baseType == null) {
throw new NullPointerException();
}
this.baseType = baseType;
this.typeFieldName = typeFieldName;
this.maintainType = maintainType;
}
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
*
* @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) {
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
}
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false);
}
/**
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
* the type field name.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
}
/**
* Ensures that this factory will handle not just the given {@code baseType}, but any subtype
* of that type.
*/
public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
this.recognizeSubtypes = true;
return this;
}
/**
* Registers {@code type} identified by {@code label}. Labels are case
* sensitive.
*
* @throws IllegalArgumentException if either {@code type} or {@code label}
* have already been registered on this type adapter.
*/
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
if (type == null || label == null) {
throw new NullPointerException();
}
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
throw new IllegalArgumentException("types and labels must be unique");
}
labelToSubtype.put(label, type);
subtypeToLabel.put(type, label);
return this;
}
/**
* Registers {@code type} identified by its {@link Class#getSimpleName simple
* name}. Labels are case sensitive.
*
* @throws IllegalArgumentException if either {@code type} or its simple name
* have already been registered on this type adapter.
*/
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}
@Override
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
if (type == null) {
return null;
}
Class<?> rawType = type.getRawType();
boolean handle =
recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType);
if (!handle) {
return null;
}
final TypeAdapter<JsonElement> jsonElementAdapter = gson.getAdapter(JsonElement.class);
final Map<String, TypeAdapter<?>> labelToDelegate = new LinkedHashMap<>();
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate = new LinkedHashMap<>();
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
labelToDelegate.put(entry.getKey(), delegate);
subtypeToDelegate.put(entry.getValue(), delegate);
}
return new TypeAdapter<R>() {
@Override public R read(JsonReader in) throws IOException {
JsonElement jsonElement = jsonElementAdapter.read(in);
JsonElement labelJsonElement;
if (maintainType) {
labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
} else {
labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
}
if (labelJsonElement == null) {
throw new JsonParseException("cannot deserialize " + baseType
+ " because it does not define a field named " + typeFieldName);
}
String label = labelJsonElement.getAsString();
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?");
}
return delegate.fromJsonTree(jsonElement);
}
@Override public void write(JsonWriter out, R value) throws IOException {
Class<?> srcType = value.getClass();
String label = subtypeToLabel.get(srcType);
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
if (delegate == null) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ "; did you forget to register a subtype?");
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
if (maintainType) {
jsonElementAdapter.write(out, jsonObject);
return;
}
JsonObject clone = new JsonObject();
if (jsonObject.has(typeFieldName)) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ " because it already defines a field named " + typeFieldName);
}
clone.add(typeFieldName, new JsonPrimitive(label));
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}
jsonElementAdapter.write(out, clone);
}
}.nullSafe();
}
}

View File

@ -1,282 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.typeadapters;
import java.io.IOException;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
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.JsonWriter;
public final class UtcDateTypeAdapter extends TypeAdapter<Date> {
private final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
@Override
public void write(JsonWriter out, Date date) throws IOException {
if (date == null) {
out.nullValue();
} else {
String value = format(date, true, UTC_TIME_ZONE);
out.value(value);
}
}
@Override
public Date read(JsonReader in) throws IOException {
try {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String date = in.nextString();
// Instead of using iso8601Format.parse(value), we use Jackson's date parsing
// This is because Android doesn't support XXX because it is JDK 1.6
return parse(date, new ParsePosition(0));
}
} catch (ParseException e) {
throw new JsonParseException(e);
}
}
// Date parsing code from Jackson databind ISO8601Utils.java
// https://github.com/FasterXML/jackson-databind/blob/master/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
private static final String GMT_ID = "GMT";
/**
* Format date into yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
*
* @param date the date to format
* @param millis true to include millis precision otherwise false
* @param tz timezone to use for the formatting (GMT will produce 'Z')
* @return the date formatted as yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
*/
private static String format(Date date, boolean millis, TimeZone tz) {
Calendar calendar = new GregorianCalendar(tz, Locale.US);
calendar.setTime(date);
// estimate capacity of buffer as close as we can (yeah, that's pedantic ;)
int capacity = "yyyy-MM-ddThh:mm:ss".length();
capacity += millis ? ".sss".length() : 0;
capacity += tz.getRawOffset() == 0 ? "Z".length() : "+hh:mm".length();
StringBuilder formatted = new StringBuilder(capacity);
padInt(formatted, calendar.get(Calendar.YEAR), "yyyy".length());
formatted.append('-');
padInt(formatted, calendar.get(Calendar.MONTH) + 1, "MM".length());
formatted.append('-');
padInt(formatted, calendar.get(Calendar.DAY_OF_MONTH), "dd".length());
formatted.append('T');
padInt(formatted, calendar.get(Calendar.HOUR_OF_DAY), "hh".length());
formatted.append(':');
padInt(formatted, calendar.get(Calendar.MINUTE), "mm".length());
formatted.append(':');
padInt(formatted, calendar.get(Calendar.SECOND), "ss".length());
if (millis) {
formatted.append('.');
padInt(formatted, calendar.get(Calendar.MILLISECOND), "sss".length());
}
int offset = tz.getOffset(calendar.getTimeInMillis());
if (offset != 0) {
int hours = Math.abs((offset / (60 * 1000)) / 60);
int minutes = Math.abs((offset / (60 * 1000)) % 60);
formatted.append(offset < 0 ? '-' : '+');
padInt(formatted, hours, "hh".length());
formatted.append(':');
padInt(formatted, minutes, "mm".length());
} else {
formatted.append('Z');
}
return formatted.toString();
}
/**
* Zero pad a number to a specified length
*
* @param buffer buffer to use for padding
* @param value the integer value to pad if necessary.
* @param length the length of the string we should zero pad
*/
private static void padInt(StringBuilder buffer, int value, int length) {
String strValue = Integer.toString(value);
for (int i = length - strValue.length(); i > 0; i--) {
buffer.append('0');
}
buffer.append(strValue);
}
/**
* Parse a date from ISO-8601 formatted string. It expects a format
* [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]
*
* @param date ISO string to parse in the appropriate format.
* @param pos The position to start parsing from, updated to where parsing stopped.
* @return the parsed date
* @throws ParseException if the date is not in the appropriate format
*/
private static Date parse(String date, ParsePosition pos) throws ParseException {
Exception fail = null;
try {
int offset = pos.getIndex();
// extract year
int year = parseInt(date, offset, offset += 4);
if (checkOffset(date, offset, '-')) {
offset += 1;
}
// extract month
int month = parseInt(date, offset, offset += 2);
if (checkOffset(date, offset, '-')) {
offset += 1;
}
// extract day
int day = parseInt(date, offset, offset += 2);
// default time value
int hour = 0;
int minutes = 0;
int seconds = 0;
int milliseconds = 0; // always use 0 otherwise returned date will include millis of current time
if (checkOffset(date, offset, 'T')) {
// extract hours, minutes, seconds and milliseconds
hour = parseInt(date, offset += 1, offset += 2);
if (checkOffset(date, offset, ':')) {
offset += 1;
}
minutes = parseInt(date, offset, offset += 2);
if (checkOffset(date, offset, ':')) {
offset += 1;
}
// second and milliseconds can be optional
if (date.length() > offset) {
char c = date.charAt(offset);
if (c != 'Z' && c != '+' && c != '-') {
seconds = parseInt(date, offset, offset += 2);
// milliseconds can be optional in the format
if (checkOffset(date, offset, '.')) {
milliseconds = parseInt(date, offset += 1, offset += 3);
}
}
}
}
// extract timezone
String timezoneId;
if (date.length() <= offset) {
throw new IllegalArgumentException("No time zone indicator");
}
char timezoneIndicator = date.charAt(offset);
if (timezoneIndicator == '+' || timezoneIndicator == '-') {
String timezoneOffset = date.substring(offset);
timezoneId = GMT_ID + timezoneOffset;
offset += timezoneOffset.length();
} else if (timezoneIndicator == 'Z') {
timezoneId = GMT_ID;
offset += 1;
} else {
throw new IndexOutOfBoundsException("Invalid time zone indicator " + timezoneIndicator);
}
TimeZone timezone = TimeZone.getTimeZone(timezoneId);
if (!timezone.getID().equals(timezoneId)) {
throw new IndexOutOfBoundsException();
}
Calendar calendar = new GregorianCalendar(timezone);
calendar.setLenient(false);
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minutes);
calendar.set(Calendar.SECOND, seconds);
calendar.set(Calendar.MILLISECOND, milliseconds);
pos.setIndex(offset);
return calendar.getTime();
// If we get a ParseException it'll already have the right message/offset.
// Other exception types can convert here.
} catch (IndexOutOfBoundsException e) {
fail = e;
} catch (NumberFormatException e) {
fail = e;
} catch (IllegalArgumentException e) {
fail = e;
}
String input = (date == null) ? null : ("'" + date + "'");
throw new ParseException("Failed to parse date [" + input + "]: " + fail.getMessage(), pos.getIndex());
}
/**
* Check if the expected character exist at the given offset in the value.
*
* @param value the string to check at the specified offset
* @param offset the offset to look for the expected character
* @param expected the expected character
* @return true if the expected character exist at the given offset
*/
private static boolean checkOffset(String value, int offset, char expected) {
return (offset < value.length()) && (value.charAt(offset) == expected);
}
/**
* Parse an integer located between 2 given offsets in a string
*
* @param value the string to parse
* @param beginIndex the start index for the integer in the string
* @param endIndex the end index for the integer in the string
* @return the int
* @throws NumberFormatException if the value is not a number
*/
private static int parseInt(String value, int beginIndex, int endIndex) throws NumberFormatException {
if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) {
throw new NumberFormatException(value);
}
// use same logic as in Integer.parseInt() but less generic we're not supporting negative values
int i = beginIndex;
int result = 0;
int digit;
if (i < endIndex) {
digit = Character.digit(value.charAt(i++), 10);
if (digit < 0) {
throw new NumberFormatException("Invalid number: " + value);
}
result = -digit;
}
while (i < endIndex) {
digit = Character.digit(value.charAt(i++), 10);
if (digit < 0) {
throw new NumberFormatException("Invalid number: " + value);
}
result *= 10;
result -= digit;
}
return -result;
}
}

View File

@ -1,195 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.graph;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
public final class GraphAdapterBuilderTest {
@Test
public void testSerialization() {
Roshambo rock = new Roshambo("ROCK");
Roshambo scissors = new Roshambo("SCISSORS");
Roshambo paper = new Roshambo("PAPER");
rock.beats = scissors;
scissors.beats = paper;
paper.beats = rock;
GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder()
.addType(Roshambo.class)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create();
assertEquals("{'0x1':{'name':'ROCK','beats':'0x2'}," +
"'0x2':{'name':'SCISSORS','beats':'0x3'}," +
"'0x3':{'name':'PAPER','beats':'0x1'}}",
gson.toJson(rock).replace('"', '\''));
}
@Test
public void testDeserialization() {
String json = "{'0x1':{'name':'ROCK','beats':'0x2'}," +
"'0x2':{'name':'SCISSORS','beats':'0x3'}," +
"'0x3':{'name':'PAPER','beats':'0x1'}}";
GsonBuilder gsonBuilder = new GsonBuilder().setLenient();
new GraphAdapterBuilder()
.addType(Roshambo.class)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create();
Roshambo rock = gson.fromJson(json, Roshambo.class);
assertEquals("ROCK", rock.name);
Roshambo scissors = rock.beats;
assertEquals("SCISSORS", scissors.name);
Roshambo paper = scissors.beats;
assertEquals("PAPER", paper.name);
assertSame(rock, paper.beats);
}
@Test
public void testDeserializationDirectSelfReference() {
String json = "{'0x1':{'name':'SUICIDE','beats':'0x1'}}";
GsonBuilder gsonBuilder = new GsonBuilder().setLenient();
new GraphAdapterBuilder()
.addType(Roshambo.class)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create();
Roshambo suicide = gson.fromJson(json, Roshambo.class);
assertEquals("SUICIDE", suicide.name);
assertSame(suicide, suicide.beats);
}
@Test
public void testSerializeListOfLists() {
Type listOfListsType = new TypeToken<List<List<?>>>() {}.getType();
Type listOfAnyType = new TypeToken<List<?>>() {}.getType();
List<List<?>> listOfLists = new ArrayList<>();
listOfLists.add(listOfLists);
listOfLists.add(new ArrayList<>());
GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder()
.addType(listOfListsType)
.addType(listOfAnyType)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create();
String json = gson.toJson(listOfLists, listOfListsType);
assertEquals("{'0x1':['0x1','0x2'],'0x2':[]}", json.replace('"', '\''));
}
@Test
public void testDeserializeListOfLists() {
Type listOfAnyType = new TypeToken<List<?>>() {}.getType();
Type listOfListsType = new TypeToken<List<List<?>>>() {}.getType();
GsonBuilder gsonBuilder = new GsonBuilder().setLenient();
new GraphAdapterBuilder()
.addType(listOfListsType)
.addType(listOfAnyType)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create();
List<List<?>> listOfLists = gson.fromJson("{'0x1':['0x1','0x2'],'0x2':[]}", listOfListsType);
assertEquals(2, listOfLists.size());
assertSame(listOfLists, listOfLists.get(0));
assertEquals(Collections.emptyList(), listOfLists.get(1));
}
@Test
public void testSerializationWithMultipleTypes() {
Company google = new Company("Google");
new Employee("Jesse", google);
new Employee("Joel", google);
GsonBuilder gsonBuilder = new GsonBuilder().setLenient();
new GraphAdapterBuilder()
.addType(Company.class)
.addType(Employee.class)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create();
assertEquals("{'0x1':{'name':'Google','employees':['0x2','0x3']},"
+ "'0x2':{'name':'Jesse','company':'0x1'},"
+ "'0x3':{'name':'Joel','company':'0x1'}}",
gson.toJson(google).replace('"', '\''));
}
@Test
public void testDeserializationWithMultipleTypes() {
GsonBuilder gsonBuilder = new GsonBuilder().setLenient();
new GraphAdapterBuilder()
.addType(Company.class)
.addType(Employee.class)
.registerOn(gsonBuilder);
Gson gson = gsonBuilder.create();
String json = "{'0x1':{'name':'Google','employees':['0x2','0x3']},"
+ "'0x2':{'name':'Jesse','company':'0x1'},"
+ "'0x3':{'name':'Joel','company':'0x1'}}";
Company company = gson.fromJson(json, Company.class);
assertEquals("Google", company.name);
Employee jesse = company.employees.get(0);
assertEquals("Jesse", jesse.name);
assertEquals(company, jesse.company);
Employee joel = company.employees.get(1);
assertEquals("Joel", joel.name);
assertEquals(company, joel.company);
}
static class Roshambo {
String name;
Roshambo beats;
Roshambo(String name) {
this.name = name;
}
}
static class Employee {
final String name;
final Company company;
Employee(String name, Company company) {
this.name = name;
this.company = company;
this.company.employees.add(this);
}
}
static class Company {
final String name;
final List<Employee> employees = new ArrayList<>();
Company(String name) {
this.name = name;
}
}
}

View File

@ -1,173 +0,0 @@
/*
* Copyright (C) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.interceptors;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import junit.framework.TestCase;
/**
* Unit tests for {@link Intercept} and {@link JsonPostDeserializer}.
*
* @author Inderjeet Singh
*/
public final class InterceptorTest extends TestCase {
private Gson gson;
@Override
public void setUp() throws Exception {
super.setUp();
this.gson = new GsonBuilder()
.registerTypeAdapterFactory(new InterceptorFactory())
.enableComplexMapKeySerialization()
.setLenient()
.create();
}
public void testExceptionsPropagated() {
try {
gson.fromJson("{}", User.class);
fail();
} catch (JsonParseException expected) {}
}
public void testTopLevelClass() {
User user = gson.fromJson("{name:'bob',password:'pwd'}", User.class);
assertEquals(User.DEFAULT_EMAIL, user.email);
}
public void testList() {
List<User> list = gson.fromJson("[{name:'bob',password:'pwd'}]", new TypeToken<List<User>>(){}.getType());
User user = list.get(0);
assertEquals(User.DEFAULT_EMAIL, user.email);
}
public void testCollection() {
Collection<User> list = gson.fromJson("[{name:'bob',password:'pwd'}]", new TypeToken<Collection<User>>(){}.getType());
User user = list.iterator().next();
assertEquals(User.DEFAULT_EMAIL, user.email);
}
public void testMapKeyAndValues() {
Type mapType = new TypeToken<Map<User, Address>>(){}.getType();
try {
gson.fromJson("[[{name:'bob',password:'pwd'},{}]]", mapType);
fail();
} catch (JsonSyntaxException expected) {}
Map<User, Address> map = gson.fromJson("[[{name:'bob',password:'pwd'},{city:'Mountain View',state:'CA',zip:'94043'}]]",
mapType);
Entry<User, Address> entry = map.entrySet().iterator().next();
assertEquals(User.DEFAULT_EMAIL, entry.getKey().email);
assertEquals(Address.DEFAULT_FIRST_LINE, entry.getValue().firstLine);
}
public void testField() {
UserGroup userGroup = gson.fromJson("{user:{name:'bob',password:'pwd'}}", UserGroup.class);
assertEquals(User.DEFAULT_EMAIL, userGroup.user.email);
}
public void testCustomTypeAdapter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(User.class, new TypeAdapter<User>() {
@Override public void write(JsonWriter out, User value) throws IOException {
throw new UnsupportedOperationException();
}
@Override public User read(JsonReader in) throws IOException {
in.beginObject();
in.nextName();
String name = in.nextString();
in.nextName();
String password = in.nextString();
in.endObject();
return new User(name, password);
}
})
.registerTypeAdapterFactory(new InterceptorFactory())
.setLenient()
.create();
UserGroup userGroup = gson.fromJson("{user:{name:'bob',password:'pwd'}}", UserGroup.class);
assertEquals(User.DEFAULT_EMAIL, userGroup.user.email);
}
public void testDirectInvocationOfTypeAdapter() throws Exception {
TypeAdapter<UserGroup> adapter = gson.getAdapter(UserGroup.class);
UserGroup userGroup = adapter.fromJson("{\"user\":{\"name\":\"bob\",\"password\":\"pwd\"}}");
assertEquals(User.DEFAULT_EMAIL, userGroup.user.email);
}
@SuppressWarnings("unused")
private static final class UserGroup {
User user;
String city;
}
@Intercept(postDeserialize = UserValidator.class)
@SuppressWarnings("unused")
private static final class User {
static final String DEFAULT_EMAIL = "invalid@invalid.com";
String name;
String password;
String email;
Address address;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
public static final class UserValidator implements JsonPostDeserializer<User> {
@Override public void postDeserialize(User user) {
if (user.name == null || user.password == null) {
throw new JsonSyntaxException("name and password are required fields.");
}
if (user.email == null) user.email = User.DEFAULT_EMAIL;
}
}
@Intercept(postDeserialize = AddressValidator.class)
@SuppressWarnings("unused")
private static final class Address {
static final String DEFAULT_FIRST_LINE = "unknown";
String firstLine;
String secondLine;
String city;
String state;
String zip;
}
public static final class AddressValidator implements JsonPostDeserializer<Address> {
@Override public void postDeserialize(Address address) {
if (address.city == null || address.state == null || address.zip == null) {
throw new JsonSyntaxException("Address city, state and zip are required fields.");
}
if (address.firstLine == null) address.firstLine = Address.DEFAULT_FIRST_LINE;
}
}
}

View File

@ -1,113 +0,0 @@
/*
* Copyright (C) 2016 Gson Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.typeadapters;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import junit.framework.TestCase;
public class PostConstructAdapterFactoryTest extends TestCase {
public void test() throws Exception {
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new PostConstructAdapterFactory())
.create();
gson.fromJson("{\"bread\": \"white\", \"cheese\": \"cheddar\"}", Sandwich.class);
try {
gson.fromJson("{\"bread\": \"cheesey bread\", \"cheese\": \"swiss\"}", Sandwich.class);
fail();
} catch (IllegalArgumentException expected) {
assertEquals("too cheesey", expected.getMessage());
}
}
public void testList() {
MultipleSandwiches sandwiches = new MultipleSandwiches(Arrays.asList(
new Sandwich("white", "cheddar"),
new Sandwich("whole wheat", "swiss")));
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory()).create();
// Throws NullPointerException without the fix in https://github.com/google/gson/pull/1103
String json = gson.toJson(sandwiches);
assertEquals("{\"sandwiches\":[{\"bread\":\"white\",\"cheese\":\"cheddar\"},{\"bread\":\"whole wheat\",\"cheese\":\"swiss\"}]}", json);
MultipleSandwiches sandwichesFromJson = gson.fromJson(json, MultipleSandwiches.class);
assertEquals(sandwiches, sandwichesFromJson);
}
@SuppressWarnings("overrides") // for missing hashCode() override
static class Sandwich {
public String bread;
public String cheese;
public Sandwich(String bread, String cheese) {
this.bread = bread;
this.cheese = cheese;
}
@PostConstruct private void validate() {
if (bread.equals("cheesey bread") && cheese != null) {
throw new IllegalArgumentException("too cheesey");
}
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Sandwich)) {
return false;
}
final Sandwich other = (Sandwich) o;
if (this.bread == null ? other.bread != null : !this.bread.equals(other.bread)) {
return false;
}
if (this.cheese == null ? other.cheese != null : !this.cheese.equals(other.cheese)) {
return false;
}
return true;
}
}
@SuppressWarnings("overrides") // for missing hashCode() override
static class MultipleSandwiches {
public List<Sandwich> sandwiches;
public MultipleSandwiches(List<Sandwich> sandwiches) {
this.sandwiches = sandwiches;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof MultipleSandwiches)) {
return false;
}
final MultipleSandwiches other = (MultipleSandwiches) o;
if (this.sandwiches == null ? other.sandwiches != null : !this.sandwiches.equals(other.sandwiches)) {
return false;
}
return true;
}
}
}

View File

@ -1,235 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.typeadapters;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapterFactory;
import junit.framework.TestCase;
public final class RuntimeTypeAdapterFactoryTest extends TestCase {
public void testRuntimeTypeAdapter() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class)
.registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(rta)
.setLenient()
.create();
CreditCard original = new CreditCard("Jesse", 234);
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
gson.toJson(original, BillingInstrument.class));
BillingInstrument deserialized = gson.fromJson(
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName);
assertTrue(deserialized instanceof CreditCard);
}
public void testRuntimeTypeAdapterRecognizeSubtypes() {
// 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
// when we call gson.toJson(original) below, without an explicit type, it should be invoked.
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class)
.recognizeSubtypes()
.registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
.setLenient()
.registerTypeAdapterFactory(rta)
.create();
CreditCard original = new CreditCard("Jesse", 234);
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
gson.toJson(original));
BillingInstrument deserialized = gson.fromJson(
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName);
assertTrue(deserialized instanceof CreditCard);
}
public void testRuntimeTypeIsBaseType() {
TypeAdapterFactory rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class)
.registerSubtype(BillingInstrument.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(rta)
.setLenient()
.create();
BillingInstrument original = new BillingInstrument("Jesse");
assertEquals("{\"type\":\"BillingInstrument\",\"ownerName\":\"Jesse\"}",
gson.toJson(original, BillingInstrument.class));
BillingInstrument deserialized = gson.fromJson(
"{type:'BillingInstrument',ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName);
}
public void testNullBaseType() {
try {
RuntimeTypeAdapterFactory.of(null);
fail();
} catch (NullPointerException expected) {
}
}
public void testNullTypeFieldName() {
try {
RuntimeTypeAdapterFactory.of(BillingInstrument.class, null);
fail();
} catch (NullPointerException expected) {
}
}
public void testNullSubtype() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class);
try {
rta.registerSubtype(null);
fail();
} catch (NullPointerException expected) {
}
}
public void testNullLabel() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class);
try {
rta.registerSubtype(CreditCard.class, null);
fail();
} catch (NullPointerException expected) {
}
}
public void testDuplicateSubtype() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class);
rta.registerSubtype(CreditCard.class, "CC");
try {
rta.registerSubtype(CreditCard.class, "Visa");
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testDuplicateLabel() {
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class);
rta.registerSubtype(CreditCard.class, "CC");
try {
rta.registerSubtype(BankTransfer.class, "CC");
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testDeserializeMissingTypeField() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class)
.registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(billingAdapter)
.create();
try {
gson.fromJson("{ownerName:'Jesse'}", BillingInstrument.class);
fail();
} catch (JsonParseException expected) {
}
}
public void testDeserializeMissingSubtype() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class)
.registerSubtype(BankTransfer.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(billingAdapter)
.create();
try {
gson.fromJson("{type:'CreditCard',ownerName:'Jesse'}", BillingInstrument.class);
fail();
} catch (JsonParseException expected) {
}
}
public void testSerializeMissingSubtype() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class)
.registerSubtype(BankTransfer.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(billingAdapter)
.create();
try {
gson.toJson(new CreditCard("Jesse", 456), BillingInstrument.class);
fail();
} catch (JsonParseException expected) {
}
}
public void testSerializeCollidingTypeFieldName() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class, "cvv")
.registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(billingAdapter)
.create();
try {
gson.toJson(new CreditCard("Jesse", 456), BillingInstrument.class);
fail();
} catch (JsonParseException expected) {
}
}
public void testSerializeWrappedNullValue() {
TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class)
.registerSubtype(CreditCard.class)
.registerSubtype(BankTransfer.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(billingAdapter)
.create();
String serialized = gson.toJson(new BillingInstrumentWrapper(null), BillingInstrumentWrapper.class);
BillingInstrumentWrapper deserialized = gson.fromJson(serialized, BillingInstrumentWrapper.class);
assertNull(deserialized.instrument);
}
static class BillingInstrumentWrapper {
BillingInstrument instrument;
BillingInstrumentWrapper(BillingInstrument instrument) {
this.instrument = instrument;
}
}
static class BillingInstrument {
private final String ownerName;
BillingInstrument(String ownerName) {
this.ownerName = ownerName;
}
}
static class CreditCard extends BillingInstrument {
int cvv;
CreditCard(String ownerName, int cvv) {
super(ownerName);
this.cvv = cvv;
}
}
static class BankTransfer extends BillingInstrument {
int bankAccount;
BankTransfer(String ownerName, int bankAccount) {
super(ownerName);
this.bankAccount = bankAccount;
}
}
}

View File

@ -1,89 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.typeadapters;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import junit.framework.TestCase;
public final class UtcDateTypeAdapterTest extends TestCase {
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, new UtcDateTypeAdapter())
.setLenient()
.create();
public void testLocalTimeZone() {
Date expected = new Date();
String json = gson.toJson(expected);
Date actual = gson.fromJson(json, Date.class);
assertEquals(expected.getTime(), actual.getTime());
}
public void testDifferentTimeZones() {
for (String timeZone : TimeZone.getAvailableIDs()) {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
Date expected = cal.getTime();
String json = gson.toJson(expected);
// System.out.println(json + ": " + timeZone);
Date actual = gson.fromJson(json, Date.class);
assertEquals(expected.getTime(), actual.getTime());
}
}
/**
* JDK 1.7 introduced support for XXX format to indicate UTC date. But Android is older JDK.
* We want to make sure that this date is parseable in Android.
*/
public void testUtcDatesOnJdkBefore1_7() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, new UtcDateTypeAdapter())
.setLenient()
.create();
gson.fromJson("'2014-12-05T04:00:00.000Z'", Date.class);
}
public void testUtcWithJdk7Default() {
Date expected = new Date();
SimpleDateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.US);
iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
String expectedJson = "\"" + iso8601Format.format(expected) + "\"";
String actualJson = gson.toJson(expected);
assertEquals(expectedJson, actualJson);
Date actual = gson.fromJson(expectedJson, Date.class);
assertEquals(expected.getTime(), actual.getTime());
}
public void testNullDateSerialization() {
String json = gson.toJson(null, Date.class);
assertEquals("null", json);
}
public void testWellFormedParseException() {
try {
gson.fromJson("2017-06-20T14:32:30", Date.class);
fail("No exception");
} catch (JsonParseException exe) {
assertEquals("java.text.ParseException: Failed to parse date ['2017-06-20T14']: 2017-06-20T14", exe.getMessage());
}
}
}

View File

@ -1,3 +0,0 @@
# metrics
This Maven module contains the source code for running internal benchmark tests against Gson.

View File

@ -1,105 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.11.0-SNAPSHOT</version>
</parent>
<artifactId>gson-metrics</artifactId>
<inceptionYear>2011</inceptionYear>
<name>Gson Metrics</name>
<description>Performance Metrics for Google Gson library</description>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<organization>
<name>Google, Inc.</name>
<url>https://www.google.com</url>
</organization>
<dependencies>
<dependency>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>com.google.caliper</groupId>
<artifactId>caliper</artifactId>
<version>1.0-beta-3</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<version>0.17.1</version>
<configuration>
<!-- This module is not supposed to be consumed as library, so no need to check API -->
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- Not deployed -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>io.gitlab.jfronny.gson</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<developers>
<developer>
<name>Inderjeet Singh</name>
<organization>Google Inc.</organization>
</developer>
<developer>
<name>Joel Leitch</name>
<organization>Google Inc.</organization>
</developer>
<developer>
<name>Jesse Wilson</name>
<organization>Google Inc.</organization>
</developer>
</developers>
</project>

View File

@ -1,88 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.metrics;
/**
* Class with a bunch of primitive fields
*
* @author Inderjeet Singh
*/
public class BagOfPrimitives {
public static final long DEFAULT_VALUE = 0;
public long longValue;
public int intValue;
public boolean booleanValue;
public String stringValue;
public BagOfPrimitives() {
this(DEFAULT_VALUE, 0, false, "");
}
public BagOfPrimitives(long longValue, int intValue, boolean booleanValue, String stringValue) {
this.longValue = longValue;
this.intValue = intValue;
this.booleanValue = booleanValue;
this.stringValue = stringValue;
}
public int getIntValue() {
return intValue;
}
public String getExpectedJson() {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\"longValue\":").append(longValue).append(",");
sb.append("\"intValue\":").append(intValue).append(",");
sb.append("\"booleanValue\":").append(booleanValue).append(",");
sb.append("\"stringValue\":\"").append(stringValue).append("\"");
sb.append("}");
return sb.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (booleanValue ? 1231 : 1237);
result = prime * result + intValue;
result = prime * result + (int) (longValue ^ (longValue >>> 32));
result = prime * result + ((stringValue == null) ? 0 : stringValue.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
BagOfPrimitives other = (BagOfPrimitives) obj;
if (booleanValue != other.booleanValue) return false;
if (intValue != other.intValue) return false;
if (longValue != other.longValue) return false;
if (stringValue == null) {
return other.stringValue == null;
} else {
return stringValue.equals(other.stringValue);
}
}
@Override
public String toString() {
return String.format("(longValue=%d,intValue=%d,booleanValue=%b,stringValue=%s)",
longValue, intValue, booleanValue, stringValue);
}
}

View File

@ -1,121 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.metrics;
import com.google.caliper.BeforeExperiment;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
/**
* Caliper based micro benchmarks for Gson
*
* @author Inderjeet Singh
* @author Jesse Wilson
* @author Joel Leitch
*/
public class BagOfPrimitivesDeserializationBenchmark {
private Gson gson;
private String json;
public static void main(String[] args) {
NonUploadingCaliperRunner.run(BagOfPrimitivesDeserializationBenchmark.class, args);
}
@BeforeExperiment
void setUp() throws Exception {
this.gson = new Gson();
BagOfPrimitives bag = new BagOfPrimitives(10L, 1, false, "foo");
this.json = gson.toJson(bag);
}
/**
* Benchmark to measure Gson performance for deserializing an object
*/
public void timeBagOfPrimitivesDefault(int reps) {
for (int i=0; i<reps; ++i) {
gson.fromJson(json, BagOfPrimitives.class);
}
}
/**
* Benchmark to measure deserializing objects by hand
*/
public void timeBagOfPrimitivesStreaming(int reps) throws IOException {
for (int i=0; i<reps; ++i) {
StringReader reader = new StringReader(json);
JsonReader jr = new JsonReader(reader);
jr.beginObject();
long longValue = 0;
int intValue = 0;
boolean booleanValue = false;
String stringValue = null;
while(jr.hasNext()) {
String name = jr.nextName();
if (name.equals("longValue")) {
longValue = jr.nextLong();
} else if (name.equals("intValue")) {
intValue = jr.nextInt();
} else if (name.equals("booleanValue")) {
booleanValue = jr.nextBoolean();
} else if (name.equals("stringValue")) {
stringValue = jr.nextString();
} else {
throw new IOException("Unexpected name: " + name);
}
}
jr.endObject();
new BagOfPrimitives(longValue, intValue, booleanValue, stringValue);
}
}
/**
* This benchmark measures the ideal Gson performance: the cost of parsing a JSON stream and
* setting object values by reflection. We should strive to reduce the discrepancy between this
* and {@link #timeBagOfPrimitivesDefault(int)} .
*/
public void timeBagOfPrimitivesReflectionStreaming(int reps) throws Exception {
for (int i=0; i<reps; ++i) {
StringReader reader = new StringReader(json);
JsonReader jr = new JsonReader(reader);
jr.beginObject();
BagOfPrimitives bag = new BagOfPrimitives();
while(jr.hasNext()) {
String name = jr.nextName();
for (Field field : BagOfPrimitives.class.getDeclaredFields()) {
if (field.getName().equals(name)) {
Class<?> fieldType = field.getType();
if (fieldType.equals(long.class)) {
field.setLong(bag, jr.nextLong());
} else if (fieldType.equals(int.class)) {
field.setInt(bag, jr.nextInt());
} else if (fieldType.equals(boolean.class)) {
field.setBoolean(bag, jr.nextBoolean());
} else if (fieldType.equals(String.class)) {
field.set(bag, jr.nextString());
} else {
throw new RuntimeException("Unexpected: type: " + fieldType + ", name: " + name);
}
}
}
}
jr.endObject();
}
}
}

View File

@ -1,139 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.metrics;
import com.google.caliper.BeforeExperiment;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* Caliper based micro benchmarks for Gson
*
* @author Inderjeet Singh
*/
public class CollectionsDeserializationBenchmark {
private static final TypeToken<List<BagOfPrimitives>> LIST_TYPE_TOKEN = new TypeToken<List<BagOfPrimitives>>(){};
private static final Type LIST_TYPE = LIST_TYPE_TOKEN.getType();
private Gson gson;
private String json;
public static void main(String[] args) {
NonUploadingCaliperRunner.run(CollectionsDeserializationBenchmark.class, args);
}
@BeforeExperiment
void setUp() throws Exception {
this.gson = new Gson();
List<BagOfPrimitives> bags = new ArrayList<>();
for (int i = 0; i < 100; ++i) {
bags.add(new BagOfPrimitives(10L, 1, false, "foo"));
}
this.json = gson.toJson(bags, LIST_TYPE);
}
/**
* Benchmark to measure Gson performance for deserializing an object
*/
public void timeCollectionsDefault(int reps) {
for (int i=0; i<reps; ++i) {
gson.fromJson(json, LIST_TYPE_TOKEN);
}
}
/**
* Benchmark to measure deserializing objects by hand
*/
public void timeCollectionsStreaming(int reps) throws IOException {
for (int i=0; i<reps; ++i) {
StringReader reader = new StringReader(json);
JsonReader jr = new JsonReader(reader);
jr.beginArray();
List<BagOfPrimitives> bags = new ArrayList<>();
while(jr.hasNext()) {
jr.beginObject();
long longValue = 0;
int intValue = 0;
boolean booleanValue = false;
String stringValue = null;
while(jr.hasNext()) {
String name = jr.nextName();
if (name.equals("longValue")) {
longValue = jr.nextLong();
} else if (name.equals("intValue")) {
intValue = jr.nextInt();
} else if (name.equals("booleanValue")) {
booleanValue = jr.nextBoolean();
} else if (name.equals("stringValue")) {
stringValue = jr.nextString();
} else {
throw new IOException("Unexpected name: " + name);
}
}
jr.endObject();
bags.add(new BagOfPrimitives(longValue, intValue, booleanValue, stringValue));
}
jr.endArray();
}
}
/**
* This benchmark measures the ideal Gson performance: the cost of parsing a JSON stream and
* setting object values by reflection. We should strive to reduce the discrepancy between this
* and {@link #timeCollectionsDefault(int)} .
*/
public void timeCollectionsReflectionStreaming(int reps) throws Exception {
for (int i=0; i<reps; ++i) {
StringReader reader = new StringReader(json);
JsonReader jr = new JsonReader(reader);
jr.beginArray();
List<BagOfPrimitives> bags = new ArrayList<>();
while(jr.hasNext()) {
jr.beginObject();
BagOfPrimitives bag = new BagOfPrimitives();
while(jr.hasNext()) {
String name = jr.nextName();
for (Field field : BagOfPrimitives.class.getDeclaredFields()) {
if (field.getName().equals(name)) {
Class<?> fieldType = field.getType();
if (fieldType.equals(long.class)) {
field.setLong(bag, jr.nextLong());
} else if (fieldType.equals(int.class)) {
field.setInt(bag, jr.nextInt());
} else if (fieldType.equals(boolean.class)) {
field.setBoolean(bag, jr.nextBoolean());
} else if (fieldType.equals(String.class)) {
field.set(bag, jr.nextString());
} else {
throw new RuntimeException("Unexpected: type: " + fieldType + ", name: " + name);
}
}
}
}
jr.endObject();
bags.add(bag);
}
jr.endArray();
}
}
}

View File

@ -1,21 +0,0 @@
package com.google.gson.metrics;
import com.google.caliper.runner.CaliperMain;
class NonUploadingCaliperRunner {
private static String[] concat(String first, String... others) {
if (others.length == 0) {
return new String[] { first };
} else {
String[] result = new String[others.length + 1];
result[0] = first;
System.arraycopy(others, 0, result, 1, others.length);
return result;
}
}
public static void run(Class<?> c, String[] args) {
// Disable result upload; Caliper uploads results to webapp by default, see https://github.com/google/caliper/issues/356
CaliperMain.main(c, concat("-Cresults.upload.options.url=", args));
}
}

View File

@ -1,428 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.metrics;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Param;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.io.CharArrayReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.Type;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Measure Gson and Jackson parsing and binding performance.
*
* <p>This benchmark requires that ParseBenchmarkData.zip is on the classpath.
* That file contains Twitter feed data, which is representative of what
* applications will be parsing.
*/
public final class ParseBenchmark {
@Param Document document;
@Param Api api;
private enum Document {
TWEETS(new TypeToken<List<Tweet>>() {}, new TypeReference<List<Tweet>>() {}),
READER_SHORT(new TypeToken<Feed>() {}, new TypeReference<Feed>() {}),
READER_LONG(new TypeToken<Feed>() {}, new TypeReference<Feed>() {});
private final TypeToken<?> gsonType;
private final TypeReference<?> jacksonType;
private Document(TypeToken<?> typeToken, TypeReference<?> typeReference) {
this.gsonType = typeToken;
this.jacksonType = typeReference;
}
}
private enum Api {
JACKSON_STREAM {
@Override Parser newParser() {
return new JacksonStreamParser();
}
},
JACKSON_BIND {
@Override Parser newParser() {
return new JacksonBindParser();
}
},
GSON_STREAM {
@Override Parser newParser() {
return new GsonStreamParser();
}
},
GSON_SKIP {
@Override Parser newParser() {
return new GsonSkipParser();
}
},
GSON_DOM {
@Override Parser newParser() {
return new GsonDomParser();
}
},
GSON_BIND {
@Override Parser newParser() {
return new GsonBindParser();
}
};
abstract Parser newParser();
}
private char[] text;
private Parser parser;
@BeforeExperiment
void setUp() throws Exception {
text = resourceToString(document.name() + ".json").toCharArray();
parser = api.newParser();
}
public void timeParse(int reps) throws Exception {
for (int i = 0; i < reps; i++) {
parser.parse(text, document);
}
}
private static File getResourceFile(String path) throws Exception {
URL url = ParseBenchmark.class.getResource(path);
if (url == null) {
throw new IllegalArgumentException("Resource " + path + " does not exist");
}
File file = new File(url.toURI());
if (!file.isFile()) {
throw new IllegalArgumentException("Resource " + path + " is not a file");
}
return file;
}
private static String resourceToString(String fileName) throws Exception {
ZipFile zipFile = new ZipFile(getResourceFile("/ParseBenchmarkData.zip"));
try {
ZipEntry zipEntry = zipFile.getEntry(fileName);
Reader reader = new InputStreamReader(zipFile.getInputStream(zipEntry));
char[] buffer = new char[8192];
StringWriter writer = new StringWriter();
int count;
while ((count = reader.read(buffer)) != -1) {
writer.write(buffer, 0, count);
}
reader.close();
return writer.toString();
} finally {
zipFile.close();
}
}
public static void main(String[] args) throws Exception {
NonUploadingCaliperRunner.run(ParseBenchmark.class, args);
}
interface Parser {
void parse(char[] data, Document document) throws Exception;
}
private static class GsonStreamParser implements Parser {
@Override
public void parse(char[] data, Document document) throws Exception {
com.google.gson.stream.JsonReader jsonReader
= new com.google.gson.stream.JsonReader(new CharArrayReader(data));
readToken(jsonReader);
jsonReader.close();
}
private void readToken(com.google.gson.stream.JsonReader reader) throws IOException {
while (true) {
switch (reader.peek()) {
case BEGIN_ARRAY:
reader.beginArray();
break;
case END_ARRAY:
reader.endArray();
break;
case BEGIN_OBJECT:
reader.beginObject();
break;
case END_OBJECT:
reader.endObject();
break;
case NAME:
reader.nextName();
break;
case BOOLEAN:
reader.nextBoolean();
break;
case NULL:
reader.nextNull();
break;
case NUMBER:
reader.nextLong();
break;
case STRING:
reader.nextString();
break;
case END_DOCUMENT:
return;
default:
throw new IllegalArgumentException("Unexpected token" + reader.peek());
}
}
}
}
private static class GsonSkipParser implements Parser {
@Override
public void parse(char[] data, Document document) throws Exception {
com.google.gson.stream.JsonReader jsonReader
= new com.google.gson.stream.JsonReader(new CharArrayReader(data));
jsonReader.skipValue();
jsonReader.close();
}
}
private static class JacksonStreamParser implements Parser {
@Override
public void parse(char[] data, Document document) throws Exception {
JsonFactory jsonFactory = new JsonFactoryBuilder().configure(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES, false).build();
com.fasterxml.jackson.core.JsonParser jp = jsonFactory.createParser(new CharArrayReader(data));
int depth = 0;
do {
JsonToken token = jp.nextToken();
switch (token) {
case START_OBJECT:
case START_ARRAY:
depth++;
break;
case END_OBJECT:
case END_ARRAY:
depth--;
break;
case FIELD_NAME:
jp.getCurrentName();
break;
case VALUE_STRING:
jp.getText();
break;
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
jp.getLongValue();
break;
case VALUE_TRUE:
case VALUE_FALSE:
jp.getBooleanValue();
break;
case VALUE_NULL:
// Do nothing; nextToken() will advance in stream
break;
default:
throw new IllegalArgumentException("Unexpected token " + token);
}
} while (depth > 0);
jp.close();
}
}
private static class GsonDomParser implements Parser {
@Override
public void parse(char[] data, Document document) throws Exception {
JsonParser.parseReader(new CharArrayReader(data));
}
}
private static class GsonBindParser implements Parser {
private static Gson gson = new GsonBuilder()
.setDateFormat("EEE MMM dd HH:mm:ss Z yyyy")
.create();
@Override
public void parse(char[] data, Document document) throws Exception {
gson.fromJson(new CharArrayReader(data), document.gsonType);
}
}
private static class JacksonBindParser implements Parser {
private static final ObjectMapper mapper;
static {
mapper = JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(MapperFeature.AUTO_DETECT_FIELDS, true)
.build();
mapper.setDateFormat(new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.ENGLISH));
}
@Override
public void parse(char[] data, Document document) throws Exception {
mapper.readValue(new CharArrayReader(data), document.jacksonType);
}
}
static class Tweet {
@JsonProperty String coordinates;
@JsonProperty boolean favorited;
@JsonProperty Date created_at;
@JsonProperty boolean truncated;
@JsonProperty Tweet retweeted_status;
@JsonProperty String id_str;
@JsonProperty String in_reply_to_id_str;
@JsonProperty String contributors;
@JsonProperty String text;
@JsonProperty long id;
@JsonProperty String retweet_count;
@JsonProperty String in_reply_to_status_id_str;
@JsonProperty Object geo;
@JsonProperty boolean retweeted;
@JsonProperty String in_reply_to_user_id;
@JsonProperty String in_reply_to_screen_name;
@JsonProperty Object place;
@JsonProperty User user;
@JsonProperty String source;
@JsonProperty String in_reply_to_user_id_str;
}
static class User {
@JsonProperty String name;
@JsonProperty String profile_sidebar_border_color;
@JsonProperty boolean profile_background_tile;
@JsonProperty String profile_sidebar_fill_color;
@JsonProperty Date created_at;
@JsonProperty String location;
@JsonProperty String profile_image_url;
@JsonProperty boolean follow_request_sent;
@JsonProperty String profile_link_color;
@JsonProperty boolean is_translator;
@JsonProperty String id_str;
@JsonProperty int favourites_count;
@JsonProperty boolean contributors_enabled;
@JsonProperty String url;
@JsonProperty boolean default_profile;
@JsonProperty long utc_offset;
@JsonProperty long id;
@JsonProperty boolean profile_use_background_image;
@JsonProperty int listed_count;
@JsonProperty String lang;
@JsonProperty("protected") @SerializedName("protected") boolean isProtected;
@JsonProperty int followers_count;
@JsonProperty String profile_text_color;
@JsonProperty String profile_background_color;
@JsonProperty String time_zone;
@JsonProperty String description;
@JsonProperty boolean notifications;
@JsonProperty boolean geo_enabled;
@JsonProperty boolean verified;
@JsonProperty String profile_background_image_url;
@JsonProperty boolean defalut_profile_image;
@JsonProperty int friends_count;
@JsonProperty int statuses_count;
@JsonProperty String screen_name;
@JsonProperty boolean following;
@JsonProperty boolean show_all_inline_media;
}
static class Feed {
@JsonProperty String id;
@JsonProperty String title;
@JsonProperty String description;
@JsonProperty("alternate") @SerializedName("alternate") List<Link> alternates;
@JsonProperty long updated;
@JsonProperty List<Item> items;
@Override public String toString() {
StringBuilder result = new StringBuilder()
.append(id)
.append("\n").append(title)
.append("\n").append(description)
.append("\n").append(alternates)
.append("\n").append(updated);
int i = 1;
for (Item item : items) {
result.append(i++).append(": ").append(item).append("\n\n");
}
return result.toString();
}
}
static class Link {
@JsonProperty String href;
@Override public String toString() {
return href;
}
}
static class Item {
@JsonProperty List<String> categories;
@JsonProperty String title;
@JsonProperty long published;
@JsonProperty long updated;
@JsonProperty("alternate") @SerializedName("alternate") List<Link> alternates;
@JsonProperty Content content;
@JsonProperty String author;
@JsonProperty List<ReaderUser> likingUsers;
@Override public String toString() {
return title
+ "\nauthor: " + author
+ "\npublished: " + published
+ "\nupdated: " + updated
+ "\n" + content
+ "\nliking users: " + likingUsers
+ "\nalternates: " + alternates
+ "\ncategories: " + categories;
}
}
static class Content {
@JsonProperty String content;
@Override public String toString() {
return content;
}
}
static class ReaderUser {
@JsonProperty String userId;
@Override public String toString() {
return userId;
}
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.metrics;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Param;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Caliper based micro benchmarks for Gson serialization
*
* @author Inderjeet Singh
* @author Jesse Wilson
* @author Joel Leitch
*/
public class SerializationBenchmark {
private Gson gson;
private BagOfPrimitives bag;
@Param
private boolean pretty;
public static void main(String[] args) {
NonUploadingCaliperRunner.run(SerializationBenchmark.class, args);
}
@BeforeExperiment
void setUp() throws Exception {
this.gson = pretty ? new GsonBuilder().setPrettyPrinting().create() : new Gson();
this.bag = new BagOfPrimitives(10L, 1, false, "foo");
}
public void timeObjectSerialization(int reps) {
for (int i=0; i<reps; ++i) {
gson.toJson(bag);
}
}
}

1
proto/.gitignore vendored
View File

@ -1 +0,0 @@
src/main/java/com/google/gson/protobuf/generated/

View File

@ -1,7 +0,0 @@
# proto
This Maven module contains the source code for a JSON serializer and deserializer for
[Protocol Buffers (protobuf)](https://developers.google.com/protocol-buffers/docs/javatutorial)
messages.
The artifacts created by this module are currently not deployed to Maven Central.

View File

@ -1,126 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.11.0-SNAPSHOT</version>
</parent>
<artifactId>proto</artifactId>
<name>Gson Protobuf Support</name>
<description>Gson support for Protobufs</description>
<licenses>
<license>
<name>Apache-2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.0.0-rc-2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>1.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>gson-proto</finalName>
<!-- Setup based on https://quarkus.io/guides/grpc-getting-started#generating-java-files-from-proto-with-protobuf-maven-plugin -->
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}</protocArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>io.gitlab.jfronny.gson</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- Not deployed -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<developers>
<developer>
<name>Inderjeet Singh</name>
<organization>Google Inc.</organization>
</developer>
</developers>
</project>

View File

@ -1,415 +0,0 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.protobuf;
import static java.util.Objects.requireNonNull;
import com.google.common.base.CaseFormat;
import com.google.common.collect.MapMaker;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.protobuf.DescriptorProtos.EnumValueOptions;
import com.google.protobuf.DescriptorProtos.FieldOptions;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.Extension;
import com.google.protobuf.Message;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
/**
* GSON type adapter for protocol buffers that knows how to serialize enums either by using their
* values or their names, and also supports custom proto field names.
* <p>
* You can specify which case representation is used for the proto fields when writing/reading the
* JSON payload by calling {@link Builder#setFieldNameSerializationFormat(CaseFormat, CaseFormat)}.
* <p>
* An example of default serialization/deserialization using custom proto field names is shown
* below:
*
* <pre>
* message MyMessage {
* // Will be serialized as 'osBuildID' instead of the default 'osBuildId'.
* string os_build_id = 1 [(serialized_name) = "osBuildID"];
* }
* </pre>
*
* @author Inderjeet Singh
* @author Emmanuel Cron
* @author Stanley Wang
*/
public class ProtoTypeAdapter
implements JsonSerializer<Message>, JsonDeserializer<Message> {
/**
* Determines how enum <u>values</u> should be serialized.
*/
public enum EnumSerialization {
/**
* Serializes and deserializes enum values using their <b>number</b>. When this is used, custom
* value names set on enums are ignored.
*/
NUMBER,
/** Serializes and deserializes enum values using their <b>name</b>. */
NAME;
}
/**
* Builder for {@link ProtoTypeAdapter}s.
*/
public static class Builder {
private final Set<Extension<FieldOptions, String>> serializedNameExtensions;
private final Set<Extension<EnumValueOptions, String>> serializedEnumValueExtensions;
private EnumSerialization enumSerialization;
private CaseFormat protoFormat;
private CaseFormat jsonFormat;
private Builder(EnumSerialization enumSerialization, CaseFormat fromFieldNameFormat,
CaseFormat toFieldNameFormat) {
this.serializedNameExtensions = new HashSet<>();
this.serializedEnumValueExtensions = new HashSet<>();
setEnumSerialization(enumSerialization);
setFieldNameSerializationFormat(fromFieldNameFormat, toFieldNameFormat);
}
public Builder setEnumSerialization(EnumSerialization enumSerialization) {
this.enumSerialization = requireNonNull(enumSerialization);
return this;
}
/**
* Sets the field names serialization format. The first parameter defines how to read the format
* of the proto field names you are converting to JSON. The second parameter defines which
* format to use when serializing them.
* <p>
* For example, if you use the following parameters: {@link CaseFormat#LOWER_UNDERSCORE},
* {@link CaseFormat#LOWER_CAMEL}, the following conversion will occur:
*
* <pre>{@code
* PROTO <-> JSON
* my_field myField
* foo foo
* n__id_ct nIdCt
* }</pre>
*/
public Builder setFieldNameSerializationFormat(CaseFormat fromFieldNameFormat,
CaseFormat toFieldNameFormat) {
this.protoFormat = fromFieldNameFormat;
this.jsonFormat = toFieldNameFormat;
return this;
}
/**
* Adds a field proto annotation that, when set, overrides the default field name
* serialization/deserialization. For example, if you add the '{@code serialized_name}'
* annotation and you define a field in your proto like the one below:
*
* <pre>
* string client_app_id = 1 [(serialized_name) = "appId"];
* </pre>
*
* ...the adapter will serialize the field using '{@code appId}' instead of the default '
* {@code clientAppId}'. This lets you customize the name serialization of any proto field.
*/
public Builder addSerializedNameExtension(
Extension<FieldOptions, String> serializedNameExtension) {
serializedNameExtensions.add(requireNonNull(serializedNameExtension));
return this;
}
/**
* Adds an enum value proto annotation that, when set, overrides the default <b>enum</b> value
* serialization/deserialization of this adapter. For example, if you add the '
* {@code serialized_value}' annotation and you define an enum in your proto like the one below:
*
* <pre>
* enum MyEnum {
* UNKNOWN = 0;
* CLIENT_APP_ID = 1 [(serialized_value) = "APP_ID"];
* TWO = 2 [(serialized_value) = "2"];
* }
* </pre>
*
* ...the adapter will serialize the value {@code CLIENT_APP_ID} as "{@code APP_ID}" and the
* value {@code TWO} as "{@code 2}". This works for both serialization and deserialization.
* <p>
* Note that you need to set the enum serialization of this adapter to
* {@link EnumSerialization#NAME}, otherwise these annotations will be ignored.
*/
public Builder addSerializedEnumValueExtension(
Extension<EnumValueOptions, String> serializedEnumValueExtension) {
serializedEnumValueExtensions.add(requireNonNull(serializedEnumValueExtension));
return this;
}
public ProtoTypeAdapter build() {
return new ProtoTypeAdapter(enumSerialization, protoFormat, jsonFormat,
serializedNameExtensions, serializedEnumValueExtensions);
}
}
/**
* Creates a new {@link ProtoTypeAdapter} builder, defaulting enum serialization to
* {@link EnumSerialization#NAME} and converting field serialization from
* {@link CaseFormat#LOWER_UNDERSCORE} to {@link CaseFormat#LOWER_CAMEL}.
*/
public static Builder newBuilder() {
return new Builder(EnumSerialization.NAME, CaseFormat.LOWER_UNDERSCORE, CaseFormat.LOWER_CAMEL);
}
private static final com.google.protobuf.Descriptors.FieldDescriptor.Type ENUM_TYPE =
com.google.protobuf.Descriptors.FieldDescriptor.Type.ENUM;
private static final ConcurrentMap<String, ConcurrentMap<Class<?>, Method>> mapOfMapOfMethods =
new MapMaker().makeMap();
private final EnumSerialization enumSerialization;
private final CaseFormat protoFormat;
private final CaseFormat jsonFormat;
private final Set<Extension<FieldOptions, String>> serializedNameExtensions;
private final Set<Extension<EnumValueOptions, String>> serializedEnumValueExtensions;
private ProtoTypeAdapter(EnumSerialization enumSerialization,
CaseFormat protoFormat,
CaseFormat jsonFormat,
Set<Extension<FieldOptions, String>> serializedNameExtensions,
Set<Extension<EnumValueOptions, String>> serializedEnumValueExtensions) {
this.enumSerialization = enumSerialization;
this.protoFormat = protoFormat;
this.jsonFormat = jsonFormat;
this.serializedNameExtensions = serializedNameExtensions;
this.serializedEnumValueExtensions = serializedEnumValueExtensions;
}
@Override
public JsonElement serialize(Message src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject ret = new JsonObject();
final Map<FieldDescriptor, Object> fields = src.getAllFields();
for (Map.Entry<FieldDescriptor, Object> fieldPair : fields.entrySet()) {
final FieldDescriptor desc = fieldPair.getKey();
String name = getCustSerializedName(desc.getOptions(), desc.getName());
if (desc.getType() == ENUM_TYPE) {
// Enum collections are also returned as ENUM_TYPE
if (fieldPair.getValue() instanceof Collection) {
// Build the array to avoid infinite loop
JsonArray array = new JsonArray();
@SuppressWarnings("unchecked")
Collection<EnumValueDescriptor> enumDescs =
(Collection<EnumValueDescriptor>) fieldPair.getValue();
for (EnumValueDescriptor enumDesc : enumDescs) {
array.add(context.serialize(getEnumValue(enumDesc)));
ret.add(name, array);
}
} else {
EnumValueDescriptor enumDesc = ((EnumValueDescriptor) fieldPair.getValue());
ret.add(name, context.serialize(getEnumValue(enumDesc)));
}
} else {
ret.add(name, context.serialize(fieldPair.getValue()));
}
}
return ret;
}
@Override
public Message deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
try {
JsonObject jsonObject = json.getAsJsonObject();
@SuppressWarnings("unchecked")
Class<? extends Message> protoClass = (Class<? extends Message>) typeOfT;
if (DynamicMessage.class.isAssignableFrom(protoClass)) {
throw new IllegalStateException("only generated messages are supported");
}
try {
// Invoke the ProtoClass.newBuilder() method
Message.Builder protoBuilder =
(Message.Builder) getCachedMethod(protoClass, "newBuilder").invoke(null);
Message defaultInstance =
(Message) getCachedMethod(protoClass, "getDefaultInstance").invoke(null);
Descriptor protoDescriptor =
(Descriptor) getCachedMethod(protoClass, "getDescriptor").invoke(null);
// Call setters on all of the available fields
for (FieldDescriptor fieldDescriptor : protoDescriptor.getFields()) {
String jsonFieldName =
getCustSerializedName(fieldDescriptor.getOptions(), fieldDescriptor.getName());
JsonElement jsonElement = jsonObject.get(jsonFieldName);
if (jsonElement != null && !jsonElement.isJsonNull()) {
// Do not reuse jsonFieldName here, it might have a custom value
Object fieldValue;
if (fieldDescriptor.getType() == ENUM_TYPE) {
if (jsonElement.isJsonArray()) {
// Handling array
Collection<EnumValueDescriptor> enumCollection =
new ArrayList<>(jsonElement.getAsJsonArray().size());
for (JsonElement element : jsonElement.getAsJsonArray()) {
enumCollection.add(
findValueByNameAndExtension(fieldDescriptor.getEnumType(), element));
}
fieldValue = enumCollection;
} else {
// No array, just a plain value
fieldValue =
findValueByNameAndExtension(fieldDescriptor.getEnumType(), jsonElement);
}
protoBuilder.setField(fieldDescriptor, fieldValue);
} else if (fieldDescriptor.isRepeated()) {
// If the type is an array, then we have to grab the type from the class.
// protobuf java field names are always lower camel case
String protoArrayFieldName =
protoFormat.to(CaseFormat.LOWER_CAMEL, fieldDescriptor.getName()) + "_";
Field protoArrayField = protoClass.getDeclaredField(protoArrayFieldName);
Type protoArrayFieldType = protoArrayField.getGenericType();
fieldValue = context.deserialize(jsonElement, protoArrayFieldType);
protoBuilder.setField(fieldDescriptor, fieldValue);
} else {
Object field = defaultInstance.getField(fieldDescriptor);
fieldValue = context.deserialize(jsonElement, field.getClass());
protoBuilder.setField(fieldDescriptor, fieldValue);
}
}
}
return protoBuilder.build();
} catch (SecurityException e) {
throw new JsonParseException(e);
} catch (NoSuchMethodException e) {
throw new JsonParseException(e);
} catch (IllegalArgumentException e) {
throw new JsonParseException(e);
} catch (IllegalAccessException e) {
throw new JsonParseException(e);
} catch (InvocationTargetException e) {
throw new JsonParseException(e);
}
} catch (Exception e) {
throw new JsonParseException("Error while parsing proto", e);
}
}
/**
* Retrieves the custom field name from the given options, and if not found, returns the specified
* default name.
*/
private String getCustSerializedName(FieldOptions options, String defaultName) {
for (Extension<FieldOptions, String> extension : serializedNameExtensions) {
if (options.hasExtension(extension)) {
return options.getExtension(extension);
}
}
return protoFormat.to(jsonFormat, defaultName);
}
/**
* Retrieves the custom enum value name from the given options, and if not found, returns the
* specified default value.
*/
private String getCustSerializedEnumValue(EnumValueOptions options, String defaultValue) {
for (Extension<EnumValueOptions, String> extension : serializedEnumValueExtensions) {
if (options.hasExtension(extension)) {
return options.getExtension(extension);
}
}
return defaultValue;
}
/**
* Returns the enum value to use for serialization, depending on the value of
* {@link EnumSerialization} that was given to this adapter.
*/
private Object getEnumValue(EnumValueDescriptor enumDesc) {
if (enumSerialization == EnumSerialization.NAME) {
return getCustSerializedEnumValue(enumDesc.getOptions(), enumDesc.getName());
} else {
return enumDesc.getNumber();
}
}
/**
* Finds an enum value in the given {@link EnumDescriptor} that matches the given JSON element,
* either by name if the current adapter is using {@link EnumSerialization#NAME}, otherwise by
* number. If matching by name, it uses the extension value if it is defined, otherwise it uses
* its default value.
*
* @throws IllegalArgumentException if a matching name/number was not found
*/
private EnumValueDescriptor findValueByNameAndExtension(EnumDescriptor desc,
JsonElement jsonElement) {
if (enumSerialization == EnumSerialization.NAME) {
// With enum name
for (EnumValueDescriptor enumDesc : desc.getValues()) {
String enumValue = getCustSerializedEnumValue(enumDesc.getOptions(), enumDesc.getName());
if (enumValue.equals(jsonElement.getAsString())) {
return enumDesc;
}
}
throw new IllegalArgumentException(
String.format("Unrecognized enum name: %s", jsonElement.getAsString()));
} else {
// With enum value
EnumValueDescriptor fieldValue = desc.findValueByNumber(jsonElement.getAsInt());
if (fieldValue == null) {
throw new IllegalArgumentException(
String.format("Unrecognized enum value: %d", jsonElement.getAsInt()));
}
return fieldValue;
}
}
private static Method getCachedMethod(Class<?> clazz, String methodName,
Class<?>... methodParamTypes) throws NoSuchMethodException {
ConcurrentMap<Class<?>, Method> mapOfMethods = mapOfMapOfMethods.get(methodName);
if (mapOfMethods == null) {
mapOfMethods = new MapMaker().makeMap();
ConcurrentMap<Class<?>, Method> previous =
mapOfMapOfMethods.putIfAbsent(methodName, mapOfMethods);
mapOfMethods = previous == null ? mapOfMethods : previous;
}
Method method = mapOfMethods.get(clazz);
if (method == null) {
method = clazz.getMethod(methodName, methodParamTypes);
mapOfMethods.putIfAbsent(clazz, method);
// NB: it doesn't matter which method we return in the event of a race.
}
return method;
}
}

View File

@ -1,226 +0,0 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.protobuf.functional;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.base.CaseFormat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.protobuf.ProtoTypeAdapter;
import com.google.gson.protobuf.ProtoTypeAdapter.EnumSerialization;
import com.google.gson.protobuf.generated.Annotations;
import com.google.gson.protobuf.generated.Bag.OuterMessage;
import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations;
import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations.InnerMessage;
import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations.InnerMessage.Data;
import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations.InnerMessage.Type;
import com.google.protobuf.GeneratedMessageV3;
import junit.framework.TestCase;
/**
* Functional tests for protocol buffers using annotations for field names and enum values.
*
* @author Emmanuel Cron
*/
public class ProtosWithAnnotationsTest extends TestCase {
private Gson gson;
private Gson gsonWithEnumNumbers;
private Gson gsonWithLowerHyphen;
@Override
protected void setUp() throws Exception {
super.setUp();
ProtoTypeAdapter.Builder protoTypeAdapter = ProtoTypeAdapter.newBuilder()
.setEnumSerialization(EnumSerialization.NAME)
.addSerializedNameExtension(Annotations.serializedName)
.addSerializedEnumValueExtension(Annotations.serializedValue);
gson = new GsonBuilder()
.registerTypeHierarchyAdapter(GeneratedMessageV3.class, protoTypeAdapter.build())
.create();
gsonWithEnumNumbers = new GsonBuilder()
.registerTypeHierarchyAdapter(GeneratedMessageV3.class, protoTypeAdapter
.setEnumSerialization(EnumSerialization.NUMBER)
.build())
.create();
gsonWithLowerHyphen = new GsonBuilder()
.registerTypeHierarchyAdapter(GeneratedMessageV3.class, protoTypeAdapter
.setFieldNameSerializationFormat(CaseFormat.LOWER_UNDERSCORE, CaseFormat.LOWER_HYPHEN)
.build())
.create();
}
public void testProtoWithAnnotations_deserialize() {
String json = String.format("{ %n"
+ " \"id\":\"41e5e7fd6065d101b97018a465ffff01\",%n"
+ " \"expiration_date\":{ %n"
+ " \"month\":\"12\",%n"
+ " \"year\":\"2017\",%n"
+ " \"timeStamp\":\"9864653135687\",%n"
+ " \"countryCode5f55\":\"en_US\"%n"
+ " },%n"
// Don't define innerMessage1
+ " \"innerMessage2\":{ %n"
// Set a number as a string; it should work
+ " \"nIdCt\":\"98798465\",%n"
+ " \"content\":\"text/plain\",%n"
+ " \"$binary_data$\":[ %n"
+ " { %n"
+ " \"data\":\"OFIN8e9fhwoeh8((⁹8efywoih\",%n"
// Don't define width
+ " \"height\":665%n"
+ " },%n"
+ " { %n"
// Define as an int; it should work
+ " \"data\":65,%n"
+ " \"width\":-56684%n"
// Don't define height
+ " }%n"
+ " ]%n"
+ " },%n"
// Define a bunch of non recognizable data
+ " \"non_existing\":\"foobar\",%n"
+ " \"stillNot\":{ %n"
+ " \"bunch\":\"of_useless data\"%n"
+ " }%n"
+ "}");
ProtoWithAnnotations proto = gson.fromJson(json, ProtoWithAnnotations.class);
assertThat(proto.getId()).isEqualTo("41e5e7fd6065d101b97018a465ffff01");
assertThat(proto.getOuterMessage()).isEqualTo(OuterMessage.newBuilder()
.setMonth(12)
.setYear(2017)
.setLongTimestamp(9864653135687L)
.setCountryCode5F55("en_US")
.build());
assertThat(proto.hasInnerMessage1()).isFalse();
assertThat(proto.getInnerMessage2()).isEqualTo(InnerMessage.newBuilder()
.setNIdCt(98798465)
.setContent(Type.TEXT)
.addData(Data.newBuilder()
.setData("OFIN8e9fhwoeh8((⁹8efywoih")
.setHeight(665))
.addData(Data.newBuilder()
.setData("65")
.setWidth(-56684))
.build());
String rebuilt = gson.toJson(proto);
assertThat(rebuilt).isEqualTo("{"
+ "\"id\":\"41e5e7fd6065d101b97018a465ffff01\","
+ "\"expiration_date\":{"
+ "\"month\":12,"
+ "\"year\":2017,"
+ "\"timeStamp\":9864653135687,"
+ "\"countryCode5f55\":\"en_US\""
+ "},"
+ "\"innerMessage2\":{"
+ "\"nIdCt\":98798465,"
+ "\"content\":\"text/plain\","
+ "\"$binary_data$\":["
+ "{"
+ "\"data\":\"OFIN8e9fhwoeh8((⁹8efywoih\","
+ "\"height\":665"
+ "},"
+ "{"
+ "\"data\":\"65\","
+ "\"width\":-56684"
+ "}]}}");
}
public void testProtoWithAnnotations_deserializeUnknownEnumValue() {
String json = String.format("{ %n"
+ " \"content\":\"UNKNOWN\"%n"
+ "}");
InnerMessage proto = gson.fromJson(json, InnerMessage.class);
assertThat(proto.getContent()).isEqualTo(Type.UNKNOWN);
}
public void testProtoWithAnnotations_deserializeUnrecognizedEnumValue() {
String json = String.format("{ %n"
+ " \"content\":\"UNRECOGNIZED\"%n"
+ "}");
try {
gson.fromJson(json, InnerMessage.class);
assertWithMessage("Should have thrown").fail();
} catch (JsonParseException e) {
// expected
}
}
public void testProtoWithAnnotations_deserializeWithEnumNumbers() {
String json = String.format("{ %n"
+ " \"content\":\"0\"%n"
+ "}");
InnerMessage proto = gsonWithEnumNumbers.fromJson(json, InnerMessage.class);
assertThat(proto.getContent()).isEqualTo(Type.UNKNOWN);
String rebuilt = gsonWithEnumNumbers.toJson(proto);
assertThat(rebuilt).isEqualTo("{\"content\":0}");
json = String.format("{ %n"
+ " \"content\":\"2\"%n"
+ "}");
proto = gsonWithEnumNumbers.fromJson(json, InnerMessage.class);
assertThat(proto.getContent()).isEqualTo(Type.IMAGE);
rebuilt = gsonWithEnumNumbers.toJson(proto);
assertThat(rebuilt).isEqualTo("{\"content\":2}");
}
public void testProtoWithAnnotations_serialize() {
ProtoWithAnnotations proto = ProtoWithAnnotations.newBuilder()
.setId("09f3j20839h032y0329hf30932h0nffn")
.setOuterMessage(OuterMessage.newBuilder()
.setMonth(14)
.setYear(6650)
.setLongTimestamp(468406876880768L))
.setInnerMessage1(InnerMessage.newBuilder()
.setNIdCt(12)
.setContent(Type.IMAGE)
.addData(Data.newBuilder()
.setData("data$$")
.setWidth(200))
.addData(Data.newBuilder()
.setHeight(56)))
.build();
String json = gsonWithLowerHyphen.toJson(proto);
assertThat(json).isEqualTo(
"{\"id\":\"09f3j20839h032y0329hf30932h0nffn\","
+ "\"expiration_date\":{"
+ "\"month\":14,"
+ "\"year\":6650,"
+ "\"timeStamp\":468406876880768"
+ "},"
// This field should be using hyphens
+ "\"inner-message-1\":{"
+ "\"n--id-ct\":12,"
+ "\"content\":2,"
+ "\"$binary_data$\":["
+ "{"
+ "\"data\":\"data$$\","
+ "\"width\":200"
+ "},"
+ "{"
+ "\"height\":56"
+ "}]"
+ "}"
+ "}");
ProtoWithAnnotations rebuilt = gsonWithLowerHyphen.fromJson(json, ProtoWithAnnotations.class);
assertThat(rebuilt).isEqualTo(proto);
}
}

View File

@ -1,102 +0,0 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.protobuf.functional;
import com.google.common.base.CaseFormat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.protobuf.ProtoTypeAdapter;
import com.google.gson.protobuf.ProtoTypeAdapter.EnumSerialization;
import com.google.gson.protobuf.generated.Bag.ProtoWithDifferentCaseFormat;
import com.google.gson.protobuf.generated.Bag.ProtoWithRepeatedFields;
import com.google.gson.protobuf.generated.Bag.SimpleProto;
import com.google.protobuf.GeneratedMessageV3;
import junit.framework.TestCase;
/**
* Functional tests for protocol buffers using complex and repeated fields
*
* @author Inderjeet Singh
*/
public class ProtosWithComplexAndRepeatedFieldsTest extends TestCase {
private Gson gson;
private Gson upperCamelGson;
@Override
protected void setUp() throws Exception {
super.setUp();
gson =
new GsonBuilder()
.registerTypeHierarchyAdapter(GeneratedMessageV3.class,
ProtoTypeAdapter.newBuilder()
.setEnumSerialization(EnumSerialization.NUMBER)
.build())
.setLenient()
.create();
upperCamelGson =
new GsonBuilder()
.registerTypeHierarchyAdapter(
GeneratedMessageV3.class, ProtoTypeAdapter.newBuilder()
.setFieldNameSerializationFormat(
CaseFormat.LOWER_UNDERSCORE, CaseFormat.UPPER_CAMEL)
.build())
.setLenient()
.create();
}
public void testSerializeRepeatedFields() {
ProtoWithRepeatedFields proto = ProtoWithRepeatedFields.newBuilder()
.addNumbers(2)
.addNumbers(3)
.addSimples(SimpleProto.newBuilder().setMsg("foo").build())
.addSimples(SimpleProto.newBuilder().setCount(3).build())
.build();
String json = gson.toJson(proto);
assertTrue(json.contains("[2,3]"));
assertTrue(json.contains("foo"));
assertTrue(json.contains("count"));
}
public void testDeserializeRepeatedFieldsProto() {
String json = "{numbers:[4,6],simples:[{msg:'bar'},{count:7}]}";
ProtoWithRepeatedFields proto =
gson.fromJson(json, ProtoWithRepeatedFields.class);
assertEquals(4, proto.getNumbers(0));
assertEquals(6, proto.getNumbers(1));
assertEquals("bar", proto.getSimples(0).getMsg());
assertEquals(7, proto.getSimples(1).getCount());
}
public void testSerializeDifferentCaseFormat() {
final ProtoWithDifferentCaseFormat proto =
ProtoWithDifferentCaseFormat.newBuilder()
.setAnotherField("foo")
.addNameThatTestsCaseFormat("bar")
.build();
final JsonObject json = upperCamelGson.toJsonTree(proto).getAsJsonObject();
assertEquals("foo", json.get("AnotherField").getAsString());
assertEquals("bar", json.get("NameThatTestsCaseFormat").getAsJsonArray().get(0).getAsString());
}
public void testDeserializeDifferentCaseFormat() {
final String json = "{NameThatTestsCaseFormat:['bar'],AnotherField:'foo'}";
ProtoWithDifferentCaseFormat proto =
upperCamelGson.fromJson(json, ProtoWithDifferentCaseFormat.class);
assertEquals("foo", proto.getAnotherField());
assertEquals("bar", proto.getNameThatTestsCaseFormat(0));
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.protobuf.functional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.protobuf.ProtoTypeAdapter;
import com.google.gson.protobuf.ProtoTypeAdapter.EnumSerialization;
import com.google.gson.protobuf.generated.Bag.SimpleProto;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.GeneratedMessageV3;
import junit.framework.TestCase;
public class ProtosWithPrimitiveTypesTest extends TestCase {
private Gson gson;
@Override
protected void setUp() throws Exception {
super.setUp();
gson = new GsonBuilder().registerTypeHierarchyAdapter(
GeneratedMessageV3.class, ProtoTypeAdapter.newBuilder()
.setEnumSerialization(EnumSerialization.NUMBER)
.build())
.setLenient()
.create();
}
public void testSerializeEmptyProto() {
SimpleProto proto = SimpleProto.newBuilder().build();
String json = gson.toJson(proto);
assertEquals("{}", json);
}
public void testDeserializeEmptyProto() {
SimpleProto proto = gson.fromJson("{}", SimpleProto.class);
assertFalse(proto.hasCount());
assertFalse(proto.hasMsg());
}
public void testSerializeProto() {
Descriptor descriptor = SimpleProto.getDescriptor();
SimpleProto proto = SimpleProto.newBuilder()
.setCount(3)
.setMsg("foo")
.build();
String json = gson.toJson(proto);
assertTrue(json.contains("\"msg\":\"foo\""));
assertTrue(json.contains("\"count\":3"));
}
public void testDeserializeProto() {
SimpleProto proto = gson.fromJson("{msg:'foo',count:3}", SimpleProto.class);
assertEquals("foo", proto.getMsg());
assertEquals(3, proto.getCount());
}
public void testDeserializeWithExplicitNullValue() {
SimpleProto proto = gson.fromJson("{msg:'foo',count:null}", SimpleProto.class);
assertEquals("foo", proto.getMsg());
assertEquals(0, proto.getCount());
}
}

View File

@ -1,32 +0,0 @@
//
// Copyright (C) 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto2";
package google.gson.protobuf.generated;
option java_package = "com.google.gson.protobuf.generated";
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
// Indicates a field name that overrides the default for serialization
optional string serialized_name = 92066888;
}
extend google.protobuf.EnumValueOptions {
// Indicates a field value that overrides the default for serialization
optional string serialized_value = 92066888;
}

View File

@ -1,72 +0,0 @@
//
// Copyright (C) 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto2";
package google.gson.protobuf.generated;
option java_package = "com.google.gson.protobuf.generated";
import "annotations.proto";
message SimpleProto {
optional string msg = 1;
optional int32 count = 2;
}
message ProtoWithDifferentCaseFormat {
repeated string name_that_tests_case_format = 1;
optional string another_field = 2;
}
message ProtoWithRepeatedFields {
repeated int64 numbers = 1;
repeated SimpleProto simples = 2;
optional string name = 3;
}
// -- A more complex message with annotations and nested protos
message OuterMessage {
optional int32 month = 1;
optional int32 year = 2;
optional int64 long_timestamp = 3 [(serialized_name) = "timeStamp"];
optional string country_code_5f55 = 4;
}
message ProtoWithAnnotations {
optional string id = 1;
optional OuterMessage outer_message = 2 [(serialized_name) = "expiration_date"];
message InnerMessage {
optional int32 n__id_ct = 1;
enum Type {
UNKNOWN = 0;
TEXT = 1 [(serialized_value) = "text/plain"];
IMAGE = 2 [(serialized_value) = "image/png"];
}
optional Type content = 2;
message Data {
optional string data = 1;
optional int32 width = 2;
optional int32 height = 3;
}
repeated Data data = 3 [(serialized_name) = "$binary_data$"];
}
optional InnerMessage inner_message_1 = 3;
optional InnerMessage inner_message_2 = 4;
}