Custom gson serialization for an abstract class - java

Maybe I'm running in the wrong direction, but I have a list of elements which I want to read.
I have an abstract base class let's call it Person:
public abstract class Person {
public int id;
public String name;
}
Now I have two possible implementations:
public class Hunter implements Person {
public int skill;
// and some more stuff
}
public class Zombie implements Person {
public int uglyness;
// and some more stuff
}
Now I have this example JSON:
[
{"id":1, "type":"zombie", "name":"Ugly Tom", "uglyness":42},
{"id":2, "type":"hunter", "name":"Shoot in leg Joe", "skill":0}
]
How can I read this JSON as List<Person>?
I'm playing for a while with TypeAdapterFactory and tried to use a class called CustomizedTypeAdapterFactory since my real structure is a little more complex as the funny example above.
I ended in that I want to delegate the serialization with this call:
return gson.getDelegateAdapter(this, resultType);
However I have no idea how I can create at runtime that TypeToken<T> which is required for this call. Any ideas?

How can I read this JSON as List?
One possibility would be to create a custom deserializer that acts like a factory.
The first step would be to define this deserializer
class PersonJsonDeserializer implements JsonDeserializer<Person> {
#Override
public Person deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String type = json.getAsJsonObject().get("type").getAsString();
switch(type) {
case "zombie":
return context.deserialize(json, Zombie.class);
case "hunter":
return context.deserialize(json, Hunter.class);
default:
throw new IllegalArgumentException("Neither zombie or hunter");
}
}
}
It fetches the value associated with the key "type" and choose the proper type to deserialize the object you're currently reading.
Then, you need to plug this deserializer within the parser.
public class GsonTest {
public static void main(String[] args) {
String json = "[\n" +
" {\"id\":1, \"type\":\"zombie\", \"name\":\"Ugly Tom\", \"uglyness\":42},\n" +
" {\"id\":2, \"type\":\"hunter\", \"name\":\"Shoot in leg Joe\", \"skill\":0}\n" +
"]";
Gson gson = new GsonBuilder().registerTypeAdapter(Person.class, new PersonJsonDeserializer()).create();
Type type = new TypeToken<List<Person>>(){}.getType();
List<Person> list = gson.fromJson(json, type);
for(Person p : list) {
System.out.println(p);
}
}
}
Running it with your example, I get:
Zombie{id=1; name=Ugly Tom; uglyness=42}
Hunter{id=2; name=Shoot in leg Joe; skill=0}
If the value of the type already corresponds to the class name, you might want to use Class.forName also:
class PersonJsonDeserializer implements JsonDeserializer<Person> {
#Override
public Person deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String className = json.getAsJsonObject().get("type").getAsString();
className = Character.toUpperCase(className.charAt(0)) + className.substring(1);
try {
return context.deserialize(json, Class.forName(className));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}

Related

GSON deserialization with generic types and generic field names

Let's say we have structure like this:
JSON:
{
"body": {
"cats": [
{
"cat": {
"id": 1,
"title": "cat1"
}
},
{
"cat": {
"id": 2,
"title": "cat2"
}
}
]
}
}
And corresponding POJO:
Response.class
private final Body body;
Body.class
private final Collection<CatWrapper> cats
CatWrapper.class
private final Cat cat
Cat.class
private final int id;
private final String title;
But let say now we have the same structure, but instead of Cat we receive truck
{
"body": {
"trucks": [
{
"truck": {
"id": 1,
"engine": "big",
"wheels": 12
}
},
{
"truck": {
"id": 2,
"engine": "super big",
"wheels": 128
}
}
]
}
}
I'm using GSON (default Retrofit json parser) on Android, consider this while giving solutions.
We could use generics here so response would look like:
private final Body<ListResponse<ItemWrapper<T>> but we can't since the field names are specific to a class.
QUESTION:
What Can I do to serialize it automaticaly without creating so many boilerplate classes? I don't really need separate classes like BodyCat, BodyTruck, BodyAnyOtherClassInThisStructure and I'm looking for a way to avoid having them.
#EDIT:
I've change classes (cat, dog -> cat,truck) due to inheritance confusion, classes used here as example DO NOT extends one another
One idea would be to define a custom generic deserializer. Its generic type will represent the concrete class of the list's elements wrapped in a Body instance.
Assuming the following classes:
class Body<T> {
private List<T> list;
public Body(List<T> list) {
this.list = list;
}
}
class Cat {
private int id;
private String title;
...
}
class Truck {
private int id;
private String engine;
private int wheels;
...
}
The deserializer assumes that the structure of the json is always the same, in the sense that you have an object that contains an object named "body". Also it assumes that the value in the first key-value pair of this body is a list.
Now for each element in the json array, we need again to fetch the inner object associated with each key. We deserialize this value and put it in the list.
class CustomJsonDeserializer<T> implements JsonDeserializer<Body<T>> {
private final Class<T> clazz;
public CustomJsonDeserializer(Class<T> clazz) {
this.clazz = clazz;
}
#Override
public Body<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject body = json.getAsJsonObject().getAsJsonObject("body");
JsonArray arr = body.entrySet().iterator().next().getValue().getAsJsonArray();
List<T> list = new ArrayList<>();
for(JsonElement element : arr) {
JsonElement innerElement = element.getAsJsonObject().entrySet().iterator().next().getValue();
list.add(context.deserialize(innerElement, clazz));
}
return new Body<>(list);
}
}
For the final step, you just need to create the corresponding type, instantiate and register the adapter in the parser. For instance for trucks:
Type truckType = new TypeToken<Body<Truck>>(){}.getType();
Gson gson = new GsonBuilder()
.registerTypeAdapter(truckType, new CustomJsonDeserializer<>(Truck.class))
.create();
Body<Truck> body = gson.fromJson(new FileReader("file.json"), truckType);
You can even return the list directly from the adapter if you want to get rid of the Body class.
With the trucks you'll get [1_big_12, 2_super big_128] as output and [1_cat1, 2_cat2] with the cats.
I would be using this approach:
public class Body {
private List<Animal> animals;
}
}
public class Animal {}
public class Dog extends Animal {}
public class Cat extends Animal {}
In this case you'll have serialization w/o any boilerplates, except fact that you have to use Gson TypeAdapter for Animal class, like:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Animal.class, new AnimalSerializer())
.create();
Where TypeAdapter should look smth like:
public class AnimalSerializer implements JsonSerializer<Animal>, JsonDeserializer<Animal> {
private static final String CLASS_META_KEY="clz";
#Override
public JsonElement serialize(Animal src, Type typeOfSrc,
JsonSerializationContext context) {
JsonElement element=null;
if (src == null) {
return element;
}
if(src instanceof Cat)
element = context.serialize(src, Cat.class);
else if(src instanceof Dog)
element = context.serialize(src, Dog.class);
else
throw new IllegalArgumentException("Unspecifiad class serializer for "+src.getClass().getName());
element.getAsJsonObject().addProperty(CLASS_META_KEY, src.getClass().getCanonicalName());
return element;
}
#Override
public Field deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Class<?> clz;
Animal animal;
JsonObject object = jsonElement.getAsJsonObject();
if (object.has(CLASS_META_KEY)) {
String className = object.get(CLASS_META_KEY).getAsString();
try {
clz = Class.forName(className);
} catch (Exception e) {
Log.e(TAG, "Can't deserialize class="+className,e);
clz = Animal.class;
}
animal = context.deserialize(jsonElement, clz);
} else {
animal = context.deserialize(jsonElement, typeOfT);
}
return animal;
}
}
public class body {
private List<cats> cats;
private List<dogs> dogs;
public class cats {
private list<cat> cat;
}
public class dogs {
private list<dog> dog;
}
}
This doesn't really reduce boilerplate code, but it should prevent you from having a separate body class with lists of classes that are just lists of your actual animals. It should make for you to just have your body class, and then a class for each animal with it's stats.

Gson custom deserializer for generic types

I have a number of classes that implement a single interface type. I want to write a custom deserializer to be able to handle some special case with the json I have to deserialize. Is this possible with google gson? Here is the sample code I have so far:
Class Type:
public class MyClass implements MyInterface {
.......
}
Deserializer
public class ResponseDeserializer implements JsonDeserializer<MyInterface>
{
private Gson fGson;
public ResponseDeserializer()
{
fGson = new Gson();
}
#Override
public MyInterface deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
String jsonString = json.toString();
if(jsonString.substring(0, 0).equals("["))
{
jsonString = "{ \"parameter\":" + jsonString + "}";
}
return context.deserialize(json, typeOfT);
}
}
register and call fromJson method:
#Override
public <T extends MyInterface> T createResponse(Class<T> responseType)
{
T returnObject = new GsonBuilder().registerTypeAdapter(MyInterface.class, new ResponseDeserializer())
.create().fromJson(getEntityText(), responseType);
return returnObject;
}
thanks for the help in advance
First of all, registerTypeAdapter() is not covariant; if you want to link a TypeAdapter to an interface, you have to use a TypeAdapterFactory. See: How do I implement TypeAdapterFactory in Gson?
Secondly, why do you think that [{...},{...},{...}] is not valid Json? Of course it is:
{
"foo":[
{
"type":"bar"
},
{
"type":"baz"
}
]
}
This is a mapping of key foo an array of objects, with one member variable. Gson would automatically deserialize it with the following POJOs:
public class MyObject {
List<TypedObject> foo;
}
public class TypedObject {
String type;
}
Beyond that, I can't help you more without knowing your specific Json string. This (especially that first link) should be enough to get started, however.

GSON Case-Insensitive Enum Deserialization

I have an enum:
enum Type {
LIVE, UPCOMING, REPLAY
}
And some JSON:
{
"type": "live"
}
And a class:
class Event {
Type type;
}
When I try to deserialize the JSON, using GSON, I receive null for the Event type field, since the case of the type field in the JSON does not match that of the enum.
Events events = new Gson().fromJson(json, Event.class);
If I change the enum to the following, then all works fine:
enum Type {
live, upcoming, replay
}
However, I would like to leave the enum constants as all uppercase.
I'm assuming I need to write an adapter but haven't found any good documentation or examples.
What is the best solution?
Edit:
I was able to get a JsonDeserializer working. Is there a more generic way to write this though, as it would be unfortunate to have to write this each time there is a case mismatch between enum values and JSON strings.
protected static class TypeCaseInsensitiveEnumAdapter implements JsonDeserializer<Type> {
#Override
public Type deserialize(JsonElement json, java.lang.reflect.Type classOfT, JsonDeserializationContext context)
throws JsonParseException {
return Type.valueOf(json.getAsString().toUpperCase());
}
}
A simpler way I found (just now) to do this is to use the #SerializedName annotation. I found it in the EnumTest.java here (the Gender class around ln 195):
https://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/EnumTest.java?r=1230
This assumes that all of your Types will come in as lowercase as opposed to being "case insensitive"
public enum Type {
#SerializedName("live")
LIVE,
#SerializedName("upcoming")
UPCOMING,
#SerializedName("replay")
REPLAY;
}
This was the simplest and most generic way I found to do this. Hope it helps you.
Now you can add multiple values for #SerializedName like this:
public enum Type {
#SerializedName(value = "live", alternate = {"LIVE"})
LIVE,
#SerializedName(value = "upcoming", alternate = {"UPCOMING"})
UPCOMING,
#SerializedName(value = "replay", alternate = {"REPLAY"})
REPLAY;
}
I think it's a bit late for you but I hope it will help anyone else!
Conveniently for you, this is very close to the example given in TypeAdapterFactory's Javadoc:
public class CaseInsensitiveEnumTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (!rawType.isEnum()) {
return null;
}
final Map<String, T> lowercaseToConstant = new HashMap<String, T>();
for (T constant : rawType.getEnumConstants()) {
lowercaseToConstant.put(toLowercase(constant), constant);
}
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(toLowercase(value));
}
}
public T read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
} else {
return lowercaseToConstant.get(toLowercase(reader.nextString()));
}
}
};
}
private String toLowercase(Object o) {
return o.toString().toLowerCase(Locale.US);
}
}
This is a rather old question, but the accepted answer didn't work for me, and using #SerializedName is not enough because I want to make sure I can match "value", "Value" and "VALUE".
I managed to make a generic Adapter based on the code posted in the question:
public class UppercaseEnumAdapter implements JsonDeserializer<Enum> {
#Override
public Enum deserialize(JsonElement json, java.lang.reflect.Type type, JsonDeserializationContext context)
throws JsonParseException {
try {
if(type instanceof Class && ((Class<?>) type).isEnum())
return Enum.valueOf((Class<Enum>) type, json.getAsString().toUpperCase());
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
And to use it:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyEnum.class, new UppercaseEnumAdapter());
Gson gson = gsonBuilder.create();

How to serialize a class with an interface?

I have never done much with serialization, but am trying to use Google's gson to serialize a Java object to a file. Here is an example of my issue:
public interface Animal {
public String getName();
}
public class Cat implements Animal {
private String mName = "Cat";
private String mHabbit = "Playing with yarn";
public String getName() {
return mName;
}
public void setName(String pName) {
mName = pName;
}
public String getHabbit() {
return mHabbit;
}
public void setHabbit(String pHabbit) {
mHabbit = pHabbit;
}
}
public class Exhibit {
private String mDescription;
private Animal mAnimal;
public Exhibit() {
mDescription = "This is a public exhibit.";
}
public String getDescription() {
return mDescription;
}
public void setDescription(String pDescription) {
mDescription = pDescription;
}
public Animal getAnimal() {
return mAnimal;
}
public void setAnimal(Animal pAnimal) {
mAnimal = pAnimal;
}
}
public class GsonTest {
public static void main(String[] argv) {
Exhibit exhibit = new Exhibit();
exhibit.setAnimal(new Cat());
Gson gson = new Gson();
String jsonString = gson.toJson(exhibit);
System.out.println(jsonString);
Exhibit deserializedExhibit = gson.fromJson(jsonString, Exhibit.class);
System.out.println(deserializedExhibit);
}
}
So this serializes nicely -- but understandably drops the type information on the Animal:
{"mDescription":"This is a public exhibit.","mAnimal":{"mName":"Cat","mHabbit":"Playing with yarn"}}
This causes real problems for deserialization, though:
Exception in thread "main" java.lang.RuntimeException: No-args constructor for interface com.atg.lp.gson.Animal does not exist. Register an InstanceCreator with Gson for this type to fix this problem.
I get why this is happening, but am having trouble figuring out the proper pattern for dealing with this. I did look in the guide but it didn't address this directly.
Here is a generic solution that works for all cases where only interface is known statically.
Create serialiser/deserialiser:
final class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {
public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
final JsonObject wrapper = new JsonObject();
wrapper.addProperty("type", object.getClass().getName());
wrapper.add("data", context.serialize(object));
return wrapper;
}
public T deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
final JsonObject wrapper = (JsonObject) elem;
final JsonElement typeName = get(wrapper, "type");
final JsonElement data = get(wrapper, "data");
final Type actualType = typeForName(typeName);
return context.deserialize(data, actualType);
}
private Type typeForName(final JsonElement typeElem) {
try {
return Class.forName(typeElem.getAsString());
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
private JsonElement get(final JsonObject wrapper, String memberName) {
final JsonElement elem = wrapper.get(memberName);
if (elem == null) throw new JsonParseException("no '" + memberName + "' member found in what was expected to be an interface wrapper");
return elem;
}
}
make Gson use it for the interface type of your choice:
Gson gson = new GsonBuilder().registerTypeAdapter(Animal.class, new InterfaceAdapter<Animal>())
.create();
Put the animal as transient, it will then not be serialized.
Or you can serialize it yourself by implementing defaultWriteObject(...) and defaultReadObject(...) (I think thats what they were called...)
EDIT See the part about "Writing an Instance Creator" here.
Gson cant deserialize an interface since it doesnt know which implementing class will be used, so you need to provide an instance creator for your Animal and set a default or similar.
#Maciek solution works perfect if the declared type of the member variable is the interface / abstract class. It won't work if the declared type is sub-class / sub-interface / sub-abstract class unless we register them all through registerTypeAdapter(). We can avoid registering one by one with the use of registerTypeHierarchyAdapter, but I realize that it will cause StackOverflowError because of the infinite loop. (Please read reference section below)
In short, my workaround solution looks a bit senseless but it works without StackOverflowError.
#Override
public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
final JsonObject wrapper = new JsonObject();
wrapper.addProperty("type", object.getClass().getName());
wrapper.add("data", new Gson().toJsonTree(object));
return wrapper;
}
I used another new Gson instance of work as the default serializer / deserializer to avoid infinite loop. The drawback of this solution is you will also lose other TypeAdapter as well, if you have custom serialization for another type and it appears in the object, it will simply fail.
Still, I am hoping for a better solution.
Reference
According to Gson 2.3.1 documentation for JsonSerializationContext and JsonDeserializationContext
Invokes default serialization on the specified object passing the specific type information. It should never be invoked on the element received as a parameter of the JsonSerializer.serialize(Object, Type, JsonSerializationContext) method. Doing so will result in an infinite loop since Gson will in-turn call the custom serializer again.
and
Invokes default deserialization on the specified object. It should never be invoked on the element received as a parameter of the JsonDeserializer.deserialize(JsonElement, Type, JsonDeserializationContext) method. Doing so will result in an infinite loop since Gson will in-turn call the custom deserializer again.
This concludes that below implementation will cause infinite loop and cause StackOverflowError eventually.
#Override
public JsonElement serialize(Animal src, Type typeOfSrc,
JsonSerializationContext context) {
return context.serialize(src);
}
I had the same problem, except my interface was of primitive type (CharSequence) and not JsonObject:
if (elem instanceof JsonPrimitive){
JsonPrimitive primitiveObject = (JsonPrimitive) elem;
Type primitiveType =
primitiveObject.isBoolean() ?Boolean.class :
primitiveObject.isNumber() ? Number.class :
primitiveObject.isString() ? String.class :
String.class;
return context.deserialize(primitiveObject, primitiveType);
}
if (elem instanceof JsonObject){
JsonObject wrapper = (JsonObject) elem;
final JsonElement typeName = get(wrapper, "type");
final JsonElement data = get(wrapper, "data");
final Type actualType = typeForName(typeName);
return context.deserialize(data, actualType);
}

gson invoking standard deserialization in custom deserializer

Is it possible to write a json deserializer in gson that invokes the default behaviour first and then i can do some post processing on my object. For example:
public class FooDeserializer implements JsonDeserializer<Foo> {
public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Foo foo = context.deserialize(json, typeOfT);//Standard deserialization call?????
foo.doSomething();
return foo();
}
}
I am using gson 1.3 (I cannot use any other version as i can only use the versions in the corporate
repository)
thanks
You can do that by implementing custom TypeAdapterFactory for your object (say CustomClass.class) to be deserialized as below.
public class CustomTypeAdapterFactory implements TypeAdapterFactory {
public final TypeAdapter create(Gson gson, TypeToken type) {
return new TypeAdapter() {
#Override
public void write(JsonWriter out, Object value) throws IOException {
JsonElement tree = delegate.toJsonTree(value);
//add code for writing object
}
#Override
public Object read(JsonReader in) throws IOException {
JsonElement tree = elementAdapter.read(in);
//Add code for reading object
}
};
}
}
And then registering it with Gson as
Gson gson = new GsonBuilder().registerTypeAdapter(CustomClass.class,new CustomTypeAdapterFactory()).create();
public class FooDeserializer implements JsonDeserializer<Foo> {
public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Foo foo=new Gson().fromJson(json, Foo.class); // use default Gson object
foo.doSomething();
return foo;
}
Check out http://gsonfire.io
It's a library I made that extends Gson to handle cases like Post-serialization and Post-deserialization
Also it has many other cool features that I've needed over time with Gson.
public class YourDeserializer<Foo> extends FooDeserializer<Foo>
{
public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Foo foo = super.deserialize(json, typeOfT,context);
foo.doSomething(); //put logic
return foo();
}
}
Here's full implementation based on incomplete answer provided by #user1556622 and discussion in code.google.com/p/google-gson/issues/detail?id=43.
As a result we can serialize list of abstract Field objects and smoothly deserialize it independent on concrete implementation of specific Field and its hierarchy depth.
class MyClass { //class which we would like to serialiaze/deserialize
List<Field> fields; //field is an hierarchy of classes
}
/**
* Purpose of this adapter is simple:
* 1) put during serialization in all Field objects additional property describing class
* 2) during deserialization invoke (based on class info) necessary deserializer to create class
*/
public class FieldTypeAdapterFactory implements TypeAdapterFactory {
private static final String CLASS_META_KEY="clz";
Gson gson;
TypeToken<?> type;
TypeAdapter<Field> fieldAdapter;
TypeAdapter<JsonElement> elementAdapter;
TypeAdapterFactory taf;
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!Field.class.isAssignableFrom(type.getRawType()))
return null; // this class only serializes 'Field' and its subtypes
this.type=type;
this.gson=gson;
this.taf=this;
fieldAdapter = gson.getDelegateAdapter(taf, TypeToken.get(Field.class));
elementAdapter = gson.getAdapter(JsonElement.class);
TypeAdapter<T> result = new FieldTypeAdapter<T>();
result.nullSafe();
return result;
}
class FieldTypeAdapter<T> extends TypeAdapter<T> {
public FieldTypeAdapter() {
}
#Override
public void write(JsonWriter out, Object value) throws IOException {
if(value instanceof Field) {
JsonObject object = fieldAdapter.toJsonTree((Field )value).getAsJsonObject();
object.addProperty(CLASS_META_KEY, value.getClass().getCanonicalName());
elementAdapter.write(out, object);
}
else {
elementAdapter.write(out, (JsonElement) value);
}
}
#Override
public T read(JsonReader in) throws IOException {
JsonObject object = elementAdapter.read(in).getAsJsonObject();
if (object.has(CLASS_META_KEY)) {
String className=object.get(CLASS_META_KEY).getAsString();
try {
Class<?> clz = Class.forName(className);
TypeAdapter<?> adapter = gson.getDelegateAdapter(taf, TypeToken.get(clz));
return (T) adapter.fromJsonTree(object);
}
catch (Exception e) {
return (T )fieldAdapter.fromJsonTree(object);
}
}
else
return (T )elementAdapter.fromJsonTree(object);
}
}
}
Registration of factory:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new FieldTypeAdapterFactory())
.create();

Categories

Resources