Gson custom serialization not working for java.lang.Object - java

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);

Related

Gson deserialize abstract class in kotlin [duplicate]

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);

Gson does not correctly serialize LocalDate

I am writing an android application, where I want to serialize instances of this Anime.java class. Its superclass AnimeBase.java has a field called aired, which is of the type DateRange. This DateRange contains two fields:
public LocalDate from;
public LocalDate to;
The serialization is very straight-forward (using gson) like this:
final Gson gson = new Gson();
String data = gson.toJson(obj);
However, in my result, the from and to fields are always empty like here:
// ...
"trailer_url": "https://www.youtube.com/embed/SlNpRThS9t8?enablejsapi\u003d1\u0026wmode\u003dopaque\u0026autoplay\u003d1",
"aired": {
"from": {}
},
"episodes": 16,
// ...
Here, to was null, so it is missing (and that is okay).
Why is gson not serializing these two LocalDates? Does it have something to do with the DateRanges setter & getter (which are a bit unusual, taking a OffsetDateTime instead of a LocalDate)?
Since these classes stem from a 3rd-party library, is there a good way for me to handle this without duplicating all the model classes in my own application for serializing/deserializing them?
Take a look at https://github.com/gkopff/gson-javatime-serialisers
There are serializers for LocalDate objects.
If you choose to create your own serializer:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(new TypeToken<LocalDate>(){}.getType(), new LocalDateConverter());
Gson gson = builder.create();
...
public class LocalDateConverter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(DateTimeFormatter.ISO_LOCAL_DATE.format(src));
}
public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return DateTimeFormatter.ISO_LOCAL_DATE.parse(json.getAsString(), LocalDate::from);
}
}
I could now find the source of this problem.
Starting in Android 9, Google added something called "Restrictions on non-SDK interfaces" where they restrict the access to not publicly documented SDK interfaces in the Android dalvik runtime.
Since Gson by default uses the ReflectiveTypeAdapterFactory which itself looks for serializable fields in the object to be serialized it heavily depends on Reflection.
Google has documented this behaviour, that the used function Class.getDeclaredFields() which is used by ReflectiveTypeAdapterFactory do return only public accessible Fields, or more concrete, only fields which are whitelisted by Google.
https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces#results-of-keeping-non-sdk
In the referenced documentation, Google explicitly states the java.time.LocalDate fields as greylisted:
Ljava/time/LocalDate;->day:S,greylist-max-o
I am not sure why this access is still working in release mode and the behaviour only is present when the build is debuggable but I suppose this is something which will be removed in future Android versions too.
Because of this we added our own backwards-compatible Serializer (which is similar to the one of #k1r0, but still works with previously serialized values):
class LocalDateJsonSerializer : JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
override fun serialize(src: LocalDate, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonObject().also {
it.addProperty("year", src.year)
it.addProperty("month", src.monthValue)
it.addProperty("day", src.dayOfMonth)
}
}
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): LocalDate {
val jsonObject = json.asJsonObject
return LocalDate.of(jsonObject["year"].asInt, jsonObject["month"].asInt, jsonObject["day"].asInt)
}
}

GSON TypeAdapter: DeSerialize Polymorphic Objects Based on "Type" Field

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);

Writing a serializer for Gson using Java 8

I have a own class called MyDate and want to write a serializer of it for Gson. This code works:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyDate.class, new JsonSerializer<MyDate>() {
#Override
public JsonElement serialize(MyDate date, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(date.toString());
}
});
However I want to use the power of Java 8 and therefore tried
builder.registerTypeAdapter(MyDate.class, (date, typeOfSrc, context) ->new JsonPrimitive(date.toString()));
But here eclipse tells me
The target type of this expression must be a functional interface
What's wrong about that Java 8 code?
In order to replace an anonymous class with a lambda, the parameter must be a Single Method Interface (SMI).
This is an interface with a single abstract method.
GsonBuilder.registerTypeAdaper takes an Object as the second argument.
You need to first assign your lambda then pass in the method:
final JsonSerializer<MyDate> serializer = (date, typeOfSrc, context) -> new JsonPrimitive(date.toString());
builder.registerTypeAdapter(MyDate.class, serializer);
This way you tell compiler which SMI you would like to implement.

Using Gson How to convert Json object that contains object of an interface that implemented by many classes - Java

I have an object of UserMsg that I want to send using JSON
public class UserMsg {
private String Type;
private UserData userData;
//Getters & Setters }
UserData is an Interface
there are many classes implementing UserData .. and everytime I send the Json Object Its userData could be an object of any of these classes.
so on the Receiving side I used this code:
Gson gson = new Gson();
UserMsg usermsg = gson.fromJson(JsonObject,UserMsg.class);
and got this error:
Unable to invoke no-args constructor for interface UserData. Register
an InstanceCreator with Gson for this type may fix this problem.
I tried to Register an InstanceCreator for UserData but It doesn't work so I need the right way to make it run.
You may want to create a custom Adapter and inside the read/write methods you may set the parsing methodology in order to handle different classes implementing your UserData interface. Something like this:
public class UserMsgAdapter extends TypeAdapter {
#Override
public UserMsg read(final JsonReader in) throws IOException {
final UserMsg data = new UserMsg();
in.beginObject();
while (in.hasNext()) {
in.nextName();
if(in.nextString().equals(someType))
// create one instance of a particular implementation
}
}
in.endObject();
return data;
}
#Override
public void write(final JsonWriter out, final UserMsg msg) throws IOException {
out.beginObject();
// write according to the instance type
out.endObject();
}
}
And dont forget to register this custom adapter:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(UserMsg.class, new UserMsgAdapter());
final Gson gson = gsonBuilder.create();

Categories