Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
204 views
Welcome To Ask or Share your Answers For Others

1 Answer

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...