Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serializing List of Interfaces GSON

I came across some weird behavior in GSON.

If I have the following class structure:

public interface Animal {
    public void nothing();
}

public class Cat implements Animal {
    private String name;

    public Cat(String name) {
        super();
        this.name = name;
    }

    public Cat(){}

    @Override
        public void nothing() {
        // TODO Auto-generated method stub
        };
    }

public class Dog implements Animal {
    private String name;

    public Dog(String name) {
            super();
        this.name = name;
    }

    public Dog(){}

    @Override
    public void nothing() {
        // TODO Auto-generated method stub

    };
}

I can do this:

ArrayList<Animal> animals = new ArrayList<Animal>();
    animals.add(new Cat("Betty"));
    animals.add(new Dog("Fred"));
    System.out.println(gson.toJson(animals));

and get this output:

[{"name":"Betty"},{"name":"Fred"}]

However, if I put animals into a containing class:

public class Container {

List<Animal> animals = new ArrayList<Animal>();

public void addAnimal(Animal a){
    animals.add(a);
}
}

and call:

Container container = new Container();
container.addAnimal(new Cat("betty"));
System.out.println(gson.toJson(container));

I get:

{"animals":[{}]}

It looks like GSON can serialize a list of an interface List<Interface> when that list is by itself, but when the list is contained in another class, GSON has problems.

Any idea what I'm doing wrong?

As a side note, I can correctly deserialize a json string into the correct type using a custom deserializer. It's the serializing that is giving me issues.

Thanks

like image 569
Tyler DeWitt Avatar asked May 10 '11 15:05

Tyler DeWitt


People also ask

How do you serialize with Gson?

Serialization in the context of Gson means converting a Java object to its JSON representation. In order to do the serialization, we need to create the Gson object, which handles the conversion. Next, we need to call the function toJson() and pass the User object. Program output.

What is Gson toJSON in Java?

Overview. Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Gson is an open-source project hosted at http://code.google.com/p/google-gson.

How does Gson serialize enum?

By default, Gson serializes enums by name in lowercase. If this is not sufficient and you want some other string or an integer value, then have a look at the @SerializedName annotation. Gson can serialize and deserialize enums using the @SerializedName annotation.


2 Answers

It's enough to simply use a custom JsonSerializer that explicitly gets the class of the object (presumably Gson's getting the declared type instead, for some reason). Here's a general solution for serializing Interfaces:

public static class InterfaceSerializer<T> implements JsonSerializer<T> {
    public JsonElement serialize(T link, Type type,
                                 JsonSerializationContext context) {
        // Odd Gson quirk
        // not smart enough to use the actual type rather than the interface
        return context.serialize(link, link.getClass());
    }
}

For your example, I can do the following:

GsonBuilder gbuild = new GsonBuilder();
Gson standard = gbuild.create();

ArrayList<Animal> animals = Lists.newArrayList(new Cat("Betty"),new Dog("Fred"));
System.out.println(standard.toJson(animals));

Container c = new Container();
c.animals.addAll(animals);
System.out.println(standard.toJson(c));

Gson interfaceAware = gbuild
     .registerTypeAdapter(Animal.class, new InterfaceSerializer<>()).create();
System.out.println(interfaceAware.toJson(c));

This outputs:

[{"name":"Betty","hates":"Everything"},{"name":"Fred","loves":"Everything"}]
{"animals":[{},{}]}
{"animals":[{"name":"Betty","hates":"Everything"}, "name":"Fred","loves":"Everything"}]}

The last item being the correctly serialized string.

This isn't enough to deserialize the JSON, unfortunately, because the resulting JSON doesn't contain any type information. Check out this answer to How to serialize a class with an interface? for a way to track the object's type, and therefore serialize/deserialize the object.

like image 86
dimo414 Avatar answered Oct 04 '22 21:10

dimo414


It's far from pretty, but the solution I'm using for now is to use

JsonObject jsonObject = gson.toJsonTree(container).getAsJsonObject();

to build a JsonObject. Then I call:

jsonObject.remove("animals");
jsonObject.add("animals",gson.toJsonTree(container.getAnimals()));

and waa laa, the object in correct json form.

Bonus points: I had a list of nested containers, so I had to construct a JsonArray so that I could iterate over my containers and call my custom toJson() on each.

Moral of the story: Add Interface Lists using the

jsonObject.remove();
jsonObject.add(propertyName, property);

trick and iterate over a List of containers using a JsonArray (just using toJson() on the list doesn't call your special method on children containers).

Definitely still looking for a more natural solution.

Happy coding

like image 29
Tyler DeWitt Avatar answered Oct 04 '22 19:10

Tyler DeWitt