using Gson 2.2.2 I'm trying to serialize an array list of POJOs (Behaviors).
i have an adapter that's pretty much a copy of what i've seen online:
public class BehaviorAdapter implements JsonSerializer<Behavior> {
private static final String CLASSNAME = "CLASSNAME";
private static final String INSTANCE = "INSTANCE";
#Override
public JsonElement serialize(Behavior src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject retValue = new JsonObject();
String className = src.getClass().getCanonicalName();
retValue.addProperty(CLASSNAME, className);
JsonElement elem = context.serialize(src);
retValue.add(INSTANCE, elem);
return retValue;
}
}
The i register it like this:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeHierarchyAdapter(Behavior.class, new BehaviorAdapter());
gson = builder.create();
Then when i try to serialize my ArrayList:
String json2 = gson.toJson(behaviors);
I get a stack overflow.
It appears that on line:
JsonElement elem = context.serialize(src);
It starts a recursive loop, going again and again through my serializer. So How do i register it so that this won't happen? I need to serialize the list and maintain polymorphism.
Looks like you found the infinite loop the JsonSerializer docs warn about:
However, you should never invoke it on the src object itself since that will cause an infinite loop (Gson will call your call-back method again).
The easiest way I can think of is to create a new Gson instance that does not have the handler installed, and run your instances through that.
As an end run, you could just serialize the List<Behavior> instead:
public class BehaviorListAdapter implements JsonSerializer<List<Behavior>> {
private static final String CLASSNAME = "CLASSNAME";
private static final String INSTANCE = "INSTANCE";
#Override
public JsonElement serialize(List<Behavior> src, Type typeOfSrc,
JsonSerializationContext context) {
JsonArray array = new JsonArray();
for (Behavior behavior : src) {
JsonObject behaviorJson = new JsonObject();
String className = behavior.getClass().getCanonicalName();
behaviorJson.addProperty(CLASSNAME, className);
JsonElement elem = context.serialize(behavior);
behaviorJson.add(INSTANCE, elem);
array.add(behaviorJson);
}
return array;
}
}
GsonBuilder builder = new GsonBuilder();
// use a TypeToken to make a Type instance for a parameterized type
builder.registerTypeAdapter(
(new TypeToken<List<Behavior>>() {}).getType(),
new BehaviorListAdapter());
gson = builder.create();
Take a look at RuntimeTypeAdapterFactory. The test for that class has an example:
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class)
.registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(rta)
.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);
This class isn't in core Gson; you'll need to copy it into your project to use it.
I get what you are trying to do here, and i had the same issue.
I ended writing a simple abstract class
public abstract class TypedJsonizable extends Jsonizable {}
and registering a TypeHierarchyAdapter to my Gson instance
protected static Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter
(TypedJsonizable.class,new TypedJsonizableSerializer());
The key for this TypeAdapter is not to invoke context.serialize and context.deserialize cause this would cause an infinite loop as stated from Jeff Bowman in his answer, this TypeAdapter use reflection to avoid that.
import com.google.gson.*;
import org.apache.log4j.Logger;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
public class TypedJsonizableSerializer implements JsonSerializer<TypedJsonizable>, JsonDeserializer<TypedJsonizable> {
static final String CLASSNAME_FIELD = "_className";
Logger logger = Logger.getLogger(TypedJsonizable.class);
#Override
public JsonElement serialize(TypedJsonizable src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject contentObj = new JsonObject();
contentObj.addProperty(CLASSNAME_FIELD,src.getClass().getCanonicalName());
for (Field field : src.getClass().getDeclaredFields()) {
field.setAccessible(true);
try {
if (field.get(src)!=null)
contentObj.add(field.getName(),context.serialize(field.get(src)));
} catch (IllegalAccessException e) {
logger.error(e.getMessage(),e);
}
}
return contentObj;
}
#Override
public TypedJsonizable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String className = jsonObject.get(CLASSNAME_FIELD).getAsString();
if (className == null || className.isEmpty())
throw new JsonParseException("Cannot find _className field. Probably this instance has not been serialized using Jsonizable jsonizer");
try {
Class<?> clazz = Class.forName(className);
Class<?> realClazz = (Class<?>) typeOfT;
if (!realClazz.equals(clazz))
throw new JsonParseException(String.format("Cannot serialize object of class %s to %s", clazz.getCanonicalName(),realClazz.getCanonicalName()));
Object o = clazz.getConstructor().newInstance();
for (Field field : o.getClass().getDeclaredFields()) {
field.setAccessible(true);
if (jsonObject.has(field.getName())) {
field.set(o,context.deserialize(jsonObject.get(field.getName()) , field.getGenericType()));
}
}
return (TypedJsonizable) o;
} catch (ClassNotFoundException e) {
throw new JsonParseException(String.format("Cannot find class with name %s . Maybe the class has been refactored or sender and receiver are not using the same jars",className));
} catch (IllegalAccessException e){
throw new JsonParseException(String.format("Cannot deserialize, got illegalAccessException %s ",e.getMessage()));
} catch (NoSuchMethodException | InstantiationException | InvocationTargetException e) {
throw new JsonParseException(String.format("Cannot deserialize object of class %s, unable to create a new instance invoking empty constructor",className));
}
}
}
I've found another solution (workaround) for this issue: don't serialize base class in your class hierarchy but use descendants. For example:
...
protected static Gson gson;
...
GsonBuilder gsb = new GsonBuilder();
gsb.registerTypeAdapter(SomeBase.class, new MQPolymorphicSerializer<SomeBase>());
gson = gsb.create();
SomeBase:
public class SomeBase{
...
}
SomeDescendant:
public class SomeDescendant extends SomeBase {
...
}
Stack overflow exception case:
gson.toJson(new SomeBase());
Workaround case:
gson.toJson(new SomeDescendant());
...and finally - serializer example:
public class MQPolymorphicSerializer<T> implements JsonSerializer<T>, JsonDeserializer<T> {
private static final String CLASSNAME = "CLASSNAME";
private static final String INSTANCE = "INSTANCE";
#Override
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject retValue = new JsonObject();
String className = src.getClass().getName();
retValue.addProperty(CLASSNAME, className);
JsonElement elem = context.serialize(src);
retValue.add(INSTANCE, elem);
return retValue;
}
#Override
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
String className = prim.getAsString();
Class<?> klass = null;
try {
klass = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e.getMessage());
}
return context.deserialize(jsonObject.get(INSTANCE), klass);
}
}
Related
I have the following class
private static class ClassWithGenericType<T> {
Set<T> values;
}
If I initialize now the class with a Set of Enum-values, serialize and deserialize the object by using gson, the Set of the deserialized object does not contain the Enum-values, but the values as String.
I think this is because the generic type is thrown away through the serialization. I saw, that I could use new TypeToken<...>(){}.getType();, but the problem is, that the class above is part of a bigger object, so I cannot call gson.fromJson(classWithGenericType, typeToken) directly.
Is there a smart way of solving this problem? I thought of a TypeAdapter, which does not serialize only the values of the Set, but also it's type.
I found now a solution and created a TypeAdapter.
public class SetTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, #NonNull TypeToken<T> type) {
if (!Set.class.isAssignableFrom(type.getRawType())) {
return null;
}
return (TypeAdapter<T>) new SetTypeAdapter(gson);
}
}
public class SetTypeAdapter extends TypeAdapter<Set<?>> {
public static final String TYPE = "#type";
public static final String DATA = "#data";
private final Gson gson;
public SetTypeAdapter(#NonNull Gson gson) {
this.gson = gson;
}
#Override
public void write(final JsonWriter out, final Set<?> set
) throws IOException {
out.beginArray();
for (Object item : set) {
out.beginObject();
out.name(TYPE).value(item.getClass().getName());
out.name(DATA).jsonValue(gson.toJson(item));
out.endObject();
}
out.endArray();
}
#Override
public Set<?> read(final JsonReader in) throws IOException {
final Set<Object> set = Sets.newHashSet();
in.beginArray();
while (in.hasNext()) {
in.beginObject();
set.add(readNextObject(in));
in.endObject();
}
in.endArray();
return set;
}
private Object readNextObject(JsonReader in) throws IOException {
try {
checkNextName(in, TYPE);
Class<?> cls = Class.forName(in.nextString());
checkNextName(in, DATA);
return gson.fromJson(in, cls);
} catch (ClassNotFoundException exception) {
throw new IOException(exception);
}
}
private void checkNextName(JsonReader in, String name) throws IOException {
if (!in.nextName().equals(name)) {
throw new IOException("Name was not: " + name);
}
}
}
We can add the factory to the GsonBuilder and afterwards we are capable of serializing a Set with generic types.
var gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(new SetTypeAdapterFactory());
var gson = gsonBuilder.create();
The serialized Set has then the following structure:
[
{
"#type":<class_name_first_element>,
"#data":<first_element_as_json>
},
...
]
Is there a way to configure Gson so that it treats any failed field parse as null instead of throwing a parse exception? Ideally we could catch and log the exception -- but we want the option to keep going with the program even if some fields (or subfields) do not parse as expected.
Example:
Malformed JSON:
{
"dog": []
}
With classes:
class Farm {
public Dog dog;
}
class Dog {
public String name;
}
Gson gson = new Gson();
Farm oldMcdonald = gson.fromJson(json, Farm.class); // should not throw exception
assertNull(oldMcdonald.dog); // should pass
In Gson, it can be implemented pretty easy.
Despite the following solution, I guess, seems not to work in any case (for example, primitives), it can be enhanced if necessary.
final class JsonFailSafeTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new JsonFailSafeTypeAdapterFactory();
private JsonFailSafeTypeAdapterFactory() {
}
static TypeAdapterFactory get() {
return instance;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// We can support non-primitive types only
if ( typeToken.getRawType().isPrimitive() ) {
return null;
}
final TypeAdapter<T> delegateTypeAdapter = gson.getAdapter(typeToken);
return new JsonFailSafeTypeAdapter<>(delegateTypeAdapter);
}
private static final class JsonFailSafeTypeAdapter<T>
extends TypeAdapter<T> {
private final TypeAdapter<T> delegateTypeAdapter;
private JsonFailSafeTypeAdapter(final TypeAdapter<T> delegateTypeAdapter) {
this.delegateTypeAdapter = delegateTypeAdapter;
}
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegateTypeAdapter.write(out, value);
}
#Override
public T read(final JsonReader in)
throws IOException {
try {
return delegateTypeAdapter.read(in);
} catch ( final MalformedJsonException | RuntimeException ignored ) {
// Once we get into unexpected JSON token, let's *always* consider a fallback to the default value
// Well, the default is always `null` anyway, but we'll do more work
return fallback(in);
}
}
private static <T> T fallback(final JsonReader in)
throws IOException {
final JsonToken jsonToken = in.peek();
switch ( jsonToken ) {
case BEGIN_ARRAY:
case BEGIN_OBJECT:
case NAME:
case STRING:
case NUMBER:
case BOOLEAN:
case NULL:
// Assume we're at the beginning of a complex JSON value or a JSON primitive
in.skipValue();
break;
case END_ARRAY:
// Not sure if we skipValue() can fast-forward this one
in.endArray();
break;
case END_OBJECT:
// The same
in.endObject();
break;
case END_DOCUMENT:
// do nothing
break;
default:
throw new AssertionError(jsonToken);
}
// Just return null (at least at the moment)
return null;
}
}
}
Now just register the above type factory to handle all types (except java.lang.Object if I'm not mistaken).
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(JsonFailSafeTypeAdapterFactory.get())
.create();
public static void main(final String... args)
throws IOException {
try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q50002961.class, "farm.json") ) {
final Farm oldMcdonald = gson.fromJson(jsonReader, Farm.class);
if ( oldMcdonald.dog != null ) {
throw new AssertionError();
}
System.out.println(oldMcdonald);
}
}
Example output:
q50002961.Farm#626b2d4a
Another option is also specifying target fields if there is no need to register the factory globally. For instance:
final class Farm {
#JsonAdapter(JsonFailSafeTypeAdapterFactory.class)
final Dog dog = null;
}
I will post a solution for your problem but it would still require you to change the code on your side. For example if you have configured a property as an object and you receive an array - there is no way to map that properly. So I would suggest to change everything in your code to List and write a custom mapper that creates a list with one element when an object is received. This way you will be flexible to what you receive but you will also need to add some logic to handle problems when you have more than one objects to the array. For your example what would you do if you get 2 dogs? What is the correct behavior?
So I would do it like that:
public class MainClass {
public static <T> void main(String[] args) throws IOException {
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ArrayAdapterFactory()).create();
// Here I do the opposite - add one dog but expect a collection
String json = "{ \"dog\": {name=\"Snoopy\"} }";
Farm oldMcdonald = gson.fromJson(json, Farm.class); // should not throw exception
System.out.println("Dog:"+oldMcdonald.dog.get(0).name); //Works properly
}
}
class Farm {
#Expose
public List<Dog> dog; //All such properties become a list. You handle the situation when there are more than one values
}
class Dog {
#Expose
public String name;
}
class ArrayAdapter<T> extends TypeAdapter<List<T>> {
private Class<T> adapterclass;
public ArrayAdapter(Class<T> adapterclass) {
this.adapterclass = adapterclass;
}
public List<T> read(JsonReader reader) throws IOException {
List<T> list = new ArrayList<T>();
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new ArrayAdapterFactory())
.create();
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
T inning = gson.fromJson(reader, adapterclass);
list.add(inning);
// return null; here if you want to return null instead of list with one element
} else if (reader.peek() == JsonToken.BEGIN_ARRAY) {
reader.beginArray();
while (reader.hasNext()) {
T inning = gson.fromJson(reader, adapterclass);
list.add(inning);
}
reader.endArray();
}
return list;
}
public void write(JsonWriter writer, List<T> value) throws IOException {
}
}
class ArrayAdapterFactory implements TypeAdapterFactory {
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
TypeAdapter<T> typeAdapter = null;
try {
if (type.getRawType() == List.class)
typeAdapter = new ArrayAdapter(
(Class) ((ParameterizedType) type.getType())
.getActualTypeArguments()[0]);
} catch (Exception e) {
e.printStackTrace();
}
return typeAdapter;
}
}
Thanks to http://sachinpatil.com/blog/2012/07/03/gson/ for the idea
I wish to have a custom GSON deserializer such that whenever it is deserializing a JSON object (i.e. anything within curly brackets { ... }), it will look for a $type node and deserialize using its inbuilt deserializing capability to that type. If no $type object is found, it just does what it normal does.
So for example, I would want this to work:
{
"$type": "my.package.CustomMessage"
"payload" : {
"$type": "my.package.PayloadMessage",
"key": "value"
}
}
public class CustomMessage {
public Object payload;
}
public class PayloadMessage implements Payload {
public String key;
}
Calling: Object customMessage = gson.fromJson(jsonString, Object.class).
So currently if I change the payload type to the Payload interface:
public class CustomMessage {
public Payload payload;
}
Then the following TypeAdapaterFactory will do what I want:
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
final PojoTypeAdapter thisAdapter = this;
public T read(JsonReader reader) throws IOException {
JsonElement jsonElement = (JsonElement)elementAdapter.read(reader);
if (!jsonElement.isJsonObject()) {
return delegate.fromJsonTree(jsonElement);
}
JsonObject jsonObject = jsonElement.getAsJsonObject();
JsonElement typeElement = jsonObject.get("$type");
if (typeElement == null) {
return delegate.fromJsonTree(jsonElement);
}
try {
return (T) gson.getDelegateAdapter(
thisAdapter,
TypeToken.get(Class.forName(typeElement.getAsString()))).fromJsonTree(jsonElement);
} catch (ClassNotFoundException ex) {
throw new IOException(ex.getMessage());
}
}
However, I would like it to work when payload is of type Object or any type for that matter, and throw some sort of type match exception if it can't assign the variable.
Looking at the source for Gson, I have found what I think is the issue:
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
// user's type adapters
factories.addAll(typeAdapterFactories);
As you can see the ObjectTypeAdapter will take precedence over my factory.
The only solution as far as I can see is to use reflection to remove the ObjectTypeAdapter from the list or insert my factory before it. I have done this and it works.
I don't know how you can achieve it with Gson but you have such a feature in Genson by default.
To enable it just do:
Genson genson = new Genson.Builder().setWithClassMetadata(true).create();
You can also register aliases for your class names:
Genson genson = new Genson.Builder().addAlias("myClass", my.package.SomeClass.class).create();
This has however some limitations:
at the moment you can't change the key used to identify the type, it is #class
it must be present in your json before the other properties - but looks fine as it is the case in your examples
Works only with json objects and not arrays or litterals
This code skeleton works on your example but should be improved and tested with different scenarios.
public class PojoTypeAdapaterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
// check types we support
if (type.getRawType().isAssignableFrom(CustomMessage.class) || type.getRawType().isAssignableFrom(PayloadMessage.class)) {
return new PojoTypeAdapter<T>(gson, type);
}
else return null;
}
private class PojoTypeAdapter<T> extends TypeAdapter<T> {
private Gson gson;
private TypeToken<T> type;
private PojoTypeAdapter(final Gson gson, final TypeToken<T> type) {
this.gson = gson;
this.type = type;
}
public T read(JsonReader reader) throws IOException {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(PojoTypeAdapaterFactory.this, this.type);
final TypeAdapter<JsonElement> elementAdapter = this.gson.getAdapter(JsonElement.class);
JsonElement jsonElement = elementAdapter.read(reader);
if (!jsonElement.isJsonObject()) {
return (T) this.gson.getAdapter(JsonElement.class).fromJsonTree(jsonElement);
}
JsonObject jsonObject = jsonElement.getAsJsonObject();
JsonElement typeElement = jsonObject.get("$type");
if (typeElement == null) {
return delegate.fromJsonTree(jsonElement);
}
try {
final Class myClass = Class.forName(typeElement.getAsString());
final Object myInstance = myClass.newInstance();
final JsonObject jsonValue = jsonElement.getAsJsonObject().get("value").getAsJsonObject();
for (Map.Entry<String, JsonElement> jsonEntry : jsonValue.entrySet()) {
final Field myField = myClass.getDeclaredField(jsonEntry.getKey());
myField.setAccessible(true);
Object value = null;
if (jsonEntry.getValue().isJsonArray()) {
//value = ...;
}
else if (jsonEntry.getValue().isJsonPrimitive()) {
final TypeAdapter fieldAdapter = this.gson.getAdapter(myField.getType());
value = fieldAdapter.fromJsonTree(jsonEntry.getValue());
}
else if (jsonEntry.getValue().isJsonObject()) {
value = this.fromJsonTree(jsonEntry.getValue());
}
myField.set(myInstance, value);
}
return (T) myInstance;
}
catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InstantiationException | NoSuchFieldException | SecurityException e) {
throw new IOException(e);
}
}
#Override
public void write(final JsonWriter out, final T value) throws IOException {
out.beginObject();
out.name("$type");
out.value(value.getClass().getName());
out.name("value");
final TypeAdapter<T> delegateAdapter = (TypeAdapter<T>) this.gson.getDelegateAdapter(PojoTypeAdapaterFactory.this, TypeToken.<T>get(value.getClass()));
delegateAdapter.write(out, value);
out.endObject();
}
}
}
The generated JSON is not exactly the same though, as it contains an additional value entry:
{
"$type": "my.package.CustomMessage",
"value": {
"payload": {
"$type": "my.package.PayloadMessage",
"value": {
"key": "hello"
}
}
}
}
I have a json response that looks like this:
{
"id":"001",
"name":"Name",
"param_distance":"10",
"param_sampling":"2"
}
And I have two classes: Teste and Parameters
public class Test {
private int id;
private String name;
private Parameters params;
}
public class Parameters {
private double distance;
private int sampling;
}
My question is: is there a way to make Gson understand that some of the json attributes should go to the Parameters class, or the only way is to "manually" parse this ?
EDIT
Well, just to make my comment in #MikO's answer more readable:
I'll add a list of an object to the json output, so json response should look like this:
{
"id":"001",
"name":"Name",
"param_distance":"10",
"param_sampling":"2",
"events":[
{
"id":"01",
"value":"22.5"
},
{
"id":"02",
"value":"31.0"
}
]
}
And the Deserializer class would look like this:
public class TestDeserializer implements JsonDeserializer<Test> {
#Override
public Test deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
Test test = new Test();
test.setId(obj.get("id").getAsInt());
test.setName(obj.get("name").getAsString());
Parameters params = new Parameters();
params.setDistance(obj.get("param_distance").getAsDouble());
params.setSampling(obj.get("param_sampling").getAsInt());
test.setParameters(params);
Gson eventGson = new Gson();
Type eventsType = new TypeToken<List<Event>>(){}.getType();
List<Event> eventList = eventGson.fromJson(obj.get("events"), eventsType);
test.setEvents(eventList);
return test;
}
}
And doing:
GsonBuilder gBuilder = new GsonBuilder();
gBuilder.registerTypeAdapter(Test.class, new TestDeserializer());
Gson gson = gBuilder.create();
Test test = gson.fromJson(reader, Test.class);
Gives me the test object the way I wanted.
The way to make Gson understand it is to write a custom deserializer by creating a TypeAdapter for your Test class. You can find information in Gson's User Guide. It is not exactly a manual parsing, but it is not that different, since you have to tell Gson how to deal with each JSON value...
It should be something like this:
private class TestDeserializer implements JsonDeserializer<Test> {
public Test deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
int id = obj.get("id").getAsInt();
String name = obj.get("name").getAsString();
double distance = obj.get("param_distance").getAsDouble();
int sampling = obj.get("param_sampling").getAsInt();
//assuming you have suitable constructors...
Test test = new Test(id, name, new Parameters(distance, sampling));
return test;
}
}
Then you have to register the TypeAdapter with:
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(Test.class, new TestDeserializer());
And finally you just have to parse your JSON as usual, with:
gson.fromJson(yourJsonString, Test.class);
Gson will automatically use your custom deserializer to parse your JSON into your Test class.
For this class
public class Data {
Map<String, Class<?>> dataTypes = new HashMap<String, Class<?>>();
}
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.serializeNulls()
.create();
Data d = new Data();
d.dataTypes.put("DateParameter", java.util.Date.class);
System.out.println("Data is " + gson.toJson(d));
Prints the following:
Data is {"dataTypes":{"DateParameter":{"annotationType":null}}}
How do get the correct class type printed?
I think you have to write your own custom json de/serializer for the Class class. Try something like this:
public static class ClassSerializer implements
JsonSerializer<Class>, JsonDeserializer<Class> {
public JsonElement serialize(Class klass, Type type, JsonSerializationContext ctx) {
return new JsonPrimitive(klass.getCanonicalName());
}
public Class deserialize(JsonElement el, Type type, JsonDeserializationContext ctx) throws JsonParseException {
try {
return Class.forName(el.getAsJsonPrimitive().getAsString());
} catch (ClassNotFoundException cnfe) {
throw new JsonParseException(cnfe);
}
}
}
// ...
public static void main(String args[]) {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Class.class, new ClassSerializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.serializeNulls()
.create();
Data d = new Data();
d.dataTypes.put("DateParameter", java.util.Date.class);
gson.toJson(d); // => {"dataTypes":{"DateParameter":"java.util.Date"}}