Bye
ci/woodpecker/push/woodpecker Pipeline failed
Details
ci/woodpecker/push/woodpecker Pipeline failed
Details
This commit is contained in:
parent
79ae63f02a
commit
237f6a7153
|
@ -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.
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
* @Intercept(postDeserialize=UserValidator.class)
|
||||
* public class User {
|
||||
* String name;
|
||||
* String password;
|
||||
* String emailAddress;
|
||||
* }
|
||||
*
|
||||
* public class UserValidator implements JsonPostDeserializer<User> {
|
||||
* 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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# metrics
|
||||
|
||||
This Maven module contains the source code for running internal benchmark tests against Gson.
|
105
metrics/pom.xml
105
metrics/pom.xml
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
src/main/java/com/google/gson/protobuf/generated/
|
|
@ -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.
|
126
proto/pom.xml
126
proto/pom.xml
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue