I have a tree object in JSON format I'm trying to deserialize with Gson. Each node contains its child nodes as fields of object type Node. Node is an interface, which has several concrete class implementations. During the deserialization process, how can I communicate to Gson which concrete class to implement when deserializing the node, if I do not know a priori which type the node belongs to? Each Node has a member field specifying the type. Is there a way to access the field when the object is in serialized form, and somehow communicate the type to Gson?
Thanks!
I'd suggest adding a custom JsonDeserializer for Nodes:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
You will be able to access the JsonElement representing the node in the deserializer's method, convert that to a JsonObject, and retrieve the field that specifies the type. You can then create an instance of the correct type of Node based on that.
You will need to register both JSONSerializer and JSONDeserializer. Also you can implement a generic adapter for all your interfaces in the following way:
During Serialization : Add a META-info of the actual impl class type.
During DeSerialization : Retrieve that meta info and call the JSONDeserailize of that class
Here is the implementation that I have used for myself and works fine.
public class PropertyBasedInterfaceMarshal implements
JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_META_KEY = "CLASS_META_KEY";
#Override
public Object deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_META_KEY).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
#Override
public JsonElement serialize(Object object, Type type,
JsonSerializationContext jsonSerializationContext) {
JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass());
jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY,
object.getClass().getCanonicalName());
return jsonEle;
}
}
Then you could register this adapter for all your interfaces as follows
Gson gson = new GsonBuilder()
.registerTypeAdapter(IInterfaceOne.class,
new PropertyBasedInterfaceMarshal())
.registerTypeAdapter(IInterfaceTwo.class,
new PropertyBasedInterfaceMarshal()).create();
As far as I can tell this doesn't work for non-collection types, or more specifically, situations where the concrete type is used to serialize, and the interface type is used to deserialize. That is, if you have a simple class implementing an interface and you serialize the concrete class, then specify the interface to deserialize, you'll end up in an unrecoverable situation.
In the above example the type adapter is registered against the interface, but when you serialize using the concrete class it will not be used, meaning the CLASS_META_KEY data will never be set.
If you specify the adapter as a hierarchical adapter (thereby telling gson to use it for all types in the hierarchy), you'll end up in an infinite loop as the serializer will just keep calling itself.
Anyone know how to serialize from a concrete implementation of an interface, then deserialize using only the interface and an InstanceCreator?
By default it seems that gson will create the concrete instance, but does not set it's fields.
Issue is logged here:
http://code.google.com/p/google-gson/issues/detail?id=411&q=interface
I want to correct the above a little
public class PropertyMarshallerAbstractTask implements JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_TYPE = "CLASS_TYPE";
#Override
public Object deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_TYPE).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
#Override
public JsonElement serialize(Object object, Type type, JsonSerializationContext jsonSerializationContext) {
Gson gson = new Gson(); //without this line it will not work
gson.toJson(object, object.getClass()); //and this one
JsonElement jsonElement = gson.toJsonTree(object); //it needs to replace to another method...toJsonTree
jsonElement.getAsJsonObject().addProperty(CLASS_TYPE, object.getClass().getCanonicalName());
return jsonElement;
}
}
And then I use it:
Gson gson = new GsonBuilder()
.registerTypeAdapter(AbstractTask.class, new PropertyMarshallerOfAbstractTask())
.create();
And then I can parse List (where I keep some non-abstract classes, which inherited from Abstract Task) to Json;
And it works in the opposite direction
List<AbstractTask> abstractTasks = gson.fromJson(json, new TypeToken<List<AbstractTask>>(){}.getType());
You have to use TypeToken class from Google Gson.
You will need of course has a generic class T to make it works
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
Related
When processing a Json string with XMLGregorianCalender using Gson I get the exception:
java.lang.RuntimeException: Failed to invoke public javax.xml.datatype.XMLGregorianCalendar() with no args
The object which is de-serialized by fromJson(..) with Gson has a XMLGregorianCalender object.
What can be the possible solution for the above error?
Abstract class javax.xml.datatype.XMLGregorianCalendar can not be a instantiated by its default/no-args constructor which makes GSON to fail.
If you resolve the class that extends above mentioned and that class has a public no-args constructor you can de-serialize that directly. For example, in my case:
XMLGregorianCalendar xmlGC = gson.fromJson(strXMLGC,
com.sun.org.apache.xerces.internal
.jaxp.datatype.XMLGregorianCalendarImpl.class);
A generic way to get it working everywhere - without caring the implementing class - is to define a custom JsonDeserializer. To make de-serialing easy you could first create an adapter class that holds the data that XMLGregorianCalendar's JSON has:
#Getter
public class XMLGregoriancalendarAdapterClass {
private BigInteger year;
private int month, day, timezone, hour, minute, second;
private BigDecimal fractionalSecond;
}
Data types of each field in above class are chosen to match one specific method for constructing XMLGregorianCalendar with javax.xml.datatype.DatatypeFactory.
Having above adapter class create a de-serialiazer like:
public class XMLGregorianCalendarDeserializer
implements JsonDeserializer<XMLGregorianCalendar> {
#Override
public XMLGregorianCalendar deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
// Easily parse the adapter class first
XMLGregoriancalendarAdapterClass ac =
new Gson().fromJson(json,
XMLGregoriancalendarAdapterClass.class);
try {
// Then return a new newXMLGregorianCalendar
// using values in adapter class
return DatatypeFactory.newInstance()
.newXMLGregorianCalendar(ac.getYear(), ac.getMonth(),
ac.getDay(), ac.getHour(),
ac.getMinute(), ac.getSecond(),
ac.getFractionalSecond(), ac.getTimezone());
} catch (DatatypeConfigurationException e) {
e.printStackTrace();
}
return null;
}
}
Using above you can construct GSON like:
Gson gson = new GsonBuilder().setPrettyPrinting()
.registerTypeAdapter(
XMLGregorianCalendar.class,
new XMLGregorianCalendarDeserializer() )
.create();
after which it is just:
XMLGregorianCalendar xmlGC2 =
gson.fromJson(json, YourClassHavingXMLGregorianCalendar.class);
I am writing a custom Gson serializer
public class DogSerializer implements JsonSerializer<Object> {
#Override
public JsonElement serialize(Object src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty("name", "sasha");
return obj;
}
I am also registering the serialzer like
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Object.class, new DogSerializer());
Now if I do System.out.println(gsonBuilder.create().toJson([My-Test-Class])) the serializer is never called. I want the serializer to be called for all fields when I pass My-test-Class. Object.class doesn't seem to work. What should I do?
What should I do?
Redesign your approach if possible and bind other types. You cannot override serialization strategies for java.lang.Object and com.google.gson.JsonElement (as of 2.8.1 and prior by design):
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);
I'm getting responses from my server that look like:
{
"timestamp" : 1,
"some other data": "blah",
"result" : {
...
}
}
for a variety of calls. What I want to do client side is:
class ServerResponse<T> {
long timestamp;
T result;
}
and then deserialize that with GSON or Jackson. I've been unable to do so thanks to type erasure. I've cheated that using subclasses like this:
class SpecificResponse extends ServerRequest<SpecificType> {}
but that requires a bunch of useless classes to lie around. Anyone have a better way?
I also need to be able to handle the case of result being an array.
The typical solution to type erasure in this case is the type token hack which takes advantage of anonymous classes maintaining superclass information for use with reflection.
Jackson provides the TypeReference type as well as an ObjectMapper#readValue overload to use it.
In your example, you'd use
ServerResponse response = objectMapper.readValue(theJsonSource, new TypeReference<ServerResponse<SpecificType>>() {});
Note that this is not type-safe so you must be careful that the type you try to assign to is compatible with the generic type argument you used in the anonymous class instance creation expression.
As for supporting both single values and arrays in the JSON, you can change your field to be of some Collection type. For example,
List<T> results
Then, enable DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY.
Feature that determines whether it is acceptable to coerce non-array
(in JSON) values to work with Java collection (arrays,
java.util.Collection) types. If enabled, collection deserializers will
try to handle non-array values as if they had "implicit" surrounding
JSON array.
I support #Pillar solution to use Jackson because it is so straiforward. 2 lines of code...
Here is Gson version that will work the same way, but you will need custom deserializer and a little reflection to achieve this.
public static class CustomDeserializer implements JsonDeserializer<ServerResponse> {
#Override
public ServerResponse deserialize(JsonElement je, Type respT,
JsonDeserializationContext jdc) throws JsonParseException {
Type t = (respT instanceof ParameterizedType) ?
((ParameterizedType) respT).getActualTypeArguments()[0] :
Object.class;
JsonObject jObject = (JsonObject) je;
ServerResponse serverResponse = new ServerResponse();
//can add validation and null value check here
serverResponse.timestamp = jObject.get("timestamp").getAsLong();
JsonElement dataElement = jObject.get("result");
if (dataElement != null) {
if (dataElement.isJsonArray()) {
JsonArray array = dataElement.getAsJsonArray();
// can use ((ParameterizedType) respT).getActualTypeArguments()
// instead of new Type[]{t}
// if you 100% sure that you will always provide type
Type listT = ParameterizedTypeImpl.make(List.class, new Type[]{t}, null);
serverResponse.result = jdc.deserialize(array, listT);
} else if(dataElement.isJsonObject()) {
serverResponse.result = new ArrayList();
serverResponse.result.add(jdc.deserialize(dataElement, t));
}
}
return serverResponse;
}
}
Use case is very simmilar to Jackson:
Gson gson = new GsonBuilder()
.registerTypeAdapter(ServerResponse.class, new CustomDeserializer())
.create();
ServerResponse<MyObject> s = gson.fromJson(json, new TypeToken<ServerResponse<MyObject>>(){}.getType())
I'm using Retrofit with the default Gson parser for JSON processing. Oftentimes, I have a series of 4~5 related but slightly different objects, which are all subtypes of a common base (let's call it "BaseType"). I know we can deserialize the different JSONs to their respective child models by checking the "type" field. The most commonly prescribed way is to extend a JsonDeserializer and register it as a type adapter in the Gson instance:
class BaseTypeDeserializer implements JsonDeserializer<BaseType> {
private static final String TYPE_FIELD = "type";
#Override
public BaseType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonObject() && json.getAsJsonObject().has(TYPE_FIELD)) {
JsonObject jsonObject = json.getAsJsonObject();
final String type = jsonObject.get(TYPE_FIELD).getAsString();
if ("type_a".equals(type)) {
return context.deserialize(json, AType.class);
} else if ("type_b".equals(type)) {
return context.deserialize(json, BType.class);
} ...
// If you need to deserialize as BaseType,
// deserialize without the current context
// or you will infinite loop
return new Gson().fromJson(json, typeOfT);
} else {
// Return a blank object on error
return new BaseType();
}
}
}
However, in my experience this is really slow, and seemingly because we have to load up the entire JSON document into a JsonElement and then traverse it to find the type field. I also don't like it that this deserializer has to be run on every one of our REST calls, even though the data isn't always necessarily being mapped to a BaseType (or its children).
This foursquare blog post mentioned using TypeAdapters as an alternative but it didn't really go further with an example.
Anybody here know how to use TypeAdapterFactory to deserialize based on a 'type' field without having to read up the entire json stream into a JsonElement object tree?
The custom deserializer should only be run when you have a BaseType or a sub-classes in the deserialization data, not every request. You register it based on the type, and it is only called when gson need to serialize that type.
Do you deserialize BaseType as well as the sub-classes? If so, this line is going to kill your performance --
return new Gson().fromJson(json, typeOfT);
creation of new Gson objects is not cheap. You are creating one each time you deserialize a base class object. Moving this call to a constructor of BaseTypeDeserializer and stashing it in a member variable will improve performance (assuming you do deserialize the base class).
The issue with creating a TypeAdapter or TypeAdapterFactory for selecting type based on the field is that you need to know the type before you start consuming the stream. If the type field is part of the object, you cannot know the type at that point. The post you linked to mentions as much --
Deserializers written using TypeAdapters may be less flexible than
those written with JsonDeserializers. Imagine you want a type field to
determine what an object field deserializes to. With the streaming
API, you need to guarantee that type comes down in the response before
object.
If you can get the type before the object in the JSON stream, you can do it, otherwise your TypeAdapter implementation is probably going to mirror your current implementation, except that the first thing you do is convert to Json tree yourself so you can find the type field. That is not going to save you much over your current implementation.
If your subclasses are similar and you don't have any field conflicts between them (fields with the same name but different types), you can use a data transfer object that has all the fields. Use gson to deserialize that, and then use it create your objects.
public class MyDTO {
String type;
// Fields from BaseType
String fromBase;
// Fields from TypeA
String fromA;
// Fields from TypeB
// ...
}
public class BaseType {
String type;
String fromBase;
public BaseType(MyDTO dto) {
type = dto.type;
fromBase = dto.fromBase;
}
}
public class TypeA extends BaseType {
String fromA;
public TypeA(MyDTO dto) {
super(dto);
fromA = dto.fromA;
}
}
you can then create a TypeAdapterFactory that handles the conversion from DTO to your object --
public class BaseTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
if(BaseType.class.isAssignableFrom(type.getRawType())) {
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return newItemAdapter((TypeAdapter<BaseType>) delegate,
gson.getAdapter(new TypeToken<MyDTO>(){}));
} else {
return null;
}
}
private TypeAdapter newItemAdapter(
final TypeAdapter<BaseType> delagateAdapter,
final TypeAdapter<MyDTO> dtoAdapter) {
return new TypeAdapter<BaseType>() {
#Override
public void write(JsonWriter out, BaseType value) throws IOException {
delagateAdapter.write(out, value);
}
#Override
public BaseType read(JsonReader in) throws IOException {
MyDTO dto = dtoAdapter.read(in);
if("base".equals(dto.type)) {
return new BaseType(dto);
} else if ("type_a".equals(dto.type)) {
return new TypeA(dto);
} else {
return null;
}
}
};
}
}
and use like this --
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new BaseTypeAdapterFactory())
.create();
BaseType base = gson.fromJson(baseString, BaseType.class);
I want to deserialize json objects to specific types of objects (using Gson library) based on type field value, eg.:
[
{
"type": "type1",
"id": "131481204101",
"url": "http://something.com",
"name": "BLAH BLAH",
"icon": "SOME_STRING",
"price": "FREE",
"backgroundUrl": "SOME_STRING"
},
{
....
}
]
So type field will have different (but known) values. Based on that value I need to deserialize that json object to appropriate model object, eg.: Type1Model, Type2Model etc.
I know I can easily do that before deserialization by converting it to JSONArray, iterate through it and resolve which type it should be deserialized to. But I think it's ugly approach and I'm looking for better way. Any suggestions?
You may implement a JsonDeserializer and use it while parsing your Json value to a Java instance. I'll try to show it with a code which is going to give you the idea:
1) Define your custom JsonDeserializer class which creates different instance of classes by incoming json value's id property:
class MyTypeModelDeserializer implements JsonDeserializer<MyBaseTypeModel> {
#Override
public MyBaseTypeModel deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement jsonType = jsonObject.get("type");
String type = jsonType.getAsString();
MyBaseTypeModel typeModel = null;
if("type1".equals(type)) {
typeModel = new Type1Model();
} else if("type2".equals(type)) {
typeModel = new Type2Model();
}
// TODO : set properties of type model
return typeModel;
}
}
2) Define a base class for your different instance of java objects:
class MyBaseTypeModel {
private String type;
// TODO : add other shared fields here
}
3) Define your different instance of java objects' classes which extend your base class:
class Type1Model extends MyBaseTypeModel {
// TODO: add specific fields for this class
}
class Type2Model extends MyBaseTypeModel {
// TODO: add specific fields for this class
}
4) Use these classes while parsing your json value to a bean:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyBaseTypeModel.class, new MyTypeModelDeserializer());
Gson gson = gsonBuilder.create();
MyBaseTypeModel myTypeModel = gson.fromJson(myJsonString, MyBaseTypeModel.class);
I can not test it right now but I hope you get the idea. Also this link would be very helpful.
#stephane-k 's answer works, but it is a bit confusing and could be improved upon (see comments to his answer)
Copy https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java into your project. (It's ok; these classes are designed to be copy/pasted https://github.com/google/gson/issues/845#issuecomment-217231315)
Setup model inheritance:
// abstract is optional
abstract class BaseClass {
}
class Type1Model extends BaseClass {
}
class Type2Model extends BaseClass {
}
Setup GSON or update existing GSON:
RuntimeTypeAdapterFactory<BaseClass> typeAdapterFactory = RuntimeTypeAdapterFactory
.of(BaseClass.class, "type")
.registerSubtype(Type1Model.class, "type1")
.registerSubtype(Type2Model.class, "type2");
Gson gson = new GsonBuilder().registerTypeAdapterFactory(typeAdapterFactory)
.create();
Deserialize your JSON into base class:
String jsonString = ...
BaseClass baseInstance = gson.fromJson(jsonString, BaseClass.class);
baseInstance will be instanceof either Type1Model or Type2Model.
From here you can either code to an interface or check instanceof and cast.
use https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java
then configure it with
public static final class JsonAdapterFactory extends
RuntimeTypeAdapterFactory<MediumSummaryInfo> {
public JsonAdapterFactory() {
super(MyBaseType.class, "type");
registerSubtype(MySubtype1.class, "type1");
registerSubtype(MySubtype2.class, "type2");
}
}
and add the annotation:
#JsonAdapter(MyBaseType.JsonAdapterFactory.class)
to MyBaseType
Much better.
If you have a lot of sub types and you do not want to or cannot maintain a list of them, you can also use an annotation based approach.
Here is the required code and also some usage examples:
https://gist.github.com/LostMekka/d90ade1fe051732d6b4ac60deea4f9c2
(it is Kotlin, but can easily be ported to Java)
For me, this approach is especially appealing, since I write a small library that does not know all possible sub types at compile time.