简体   繁体   中英

Custom gson serialization for an abstract class

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM