Адаптер типа Gson и Custom Deseralizer

В приведенном ниже примере показан класс (Club), который содержит коллекцию абстрактного класса (Member). Я смущен тем, нужен ли мне TypeAdapter или JsonDeserializer для правильной работы десериализации. Сериализация работает отлично, без какой-либо помощи, но десериализация бросает исключения. Для иллюстрации я построил следующий тест "клон". Если бы кто-нибудь мог показать рабочий пример, я был бы очень благодарен.

Первый клубный класс

package gson.test;
import java.util.ArrayList;

import com.google.gson.Gson;

public class Club {
    public static void main(String[] args) {
        // Setup a Club with 2 members
        Club myClub = new Club();
        myClub.addMember(new Silver());
        myClub.addMember(new Gold());

        // Serialize to JSON
        Gson gson = new Gson();
        String myJsonClub = gson.toJson(myClub); 
        System.out.println(myJsonClub);

        // De-Serialize to Club
        Club myNewClub = gson.fromJson(myJsonClub, Club.class);
        System.out.println(myClub.equals(myNewClub) ? "Cloned!" : "Failed");
    }

    private String title = "MyClub";
    private ArrayList<Member> members = new ArrayList<Member>();

    public boolean equals(Club that) {
        if (!this.title.equals(that.title)) return false;
        for (int i=0; i<this.members.size(); i++) {
            if (! this.getMember(i).equals(that.getMember(i))) return false;
        }
        return true;
    }
    public void addMember(Member newMember) { members.add(newMember); }
    public Member getMember(int i) { return members.get(i); }
}

Теперь член абстрактного базового класса

package gson.test;
public abstract class Member {
    private int type;
    private String name = "";

    public int getType() { return type; }
    public void setType(int type) { this.type = type; }
    public boolean equals(Member that) {return this.name.equals(that.name);}
}

И два конкретных подкласса члена (золото и серебро)

package gson.test;
public class Gold extends Member {
    private String goldData = "SomeGoldData";
    public Gold() {
        super();
        this.setType(2);
    }
    public boolean equals(Gold that) {
        return (super.equals(that) && this.goldData.equals(that.goldData)); 
    }
}

package gson.test;
public class Silver extends Member {
    private String silverData = "SomeSilverData";
    public Silver() {
        super();
        this.setType(1);
    }
    public boolean equals(Silver that) { 
        return (super.equals(that) && this.silverData.equals(that.silverData)); 
    }
}

И, наконец, вывод

    {"title":"MyClub","members":[{"silverData":"SomeSilverData","type":1,"name":""},{"goldData":"SomeGoldData","type":2,"name":""}]}
    Exception in thread "main" java.lang.RuntimeException: Failed to invoke public gson.test.Member() with no args
        at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:107)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:186)
...

Ответ 1

Вы можете сделать то и другое. Какой из них вы выбираете, зависит от потенциального воздействия на производительность и того, сколько кода готовы писать.

Десериализаторы стоят дороже. Это связано с тем, что вход в десериализатор является деревом json, и GSon должен будет создать полное поддерево JsonElement свойства, которое соответствует вашему классу, прежде чем оно сможет передать его вашему десериализатору. Если у ваших классов много гнездования, эта стоимость увеличивается. Для простых объектов это будет незначительно.

Кажется, что вы будете знать, какой класс создать на основе значения свойства type, которое будет включено в целевой объект. Ваш десериализатор должен

  • просмотрите переданный объект JsonElement, прочитайте свойство type, определите тип
  • вызов context.deserialize() с классом и тем же самым элементом, который был передан вам
  • введите ошибку, если тип отсутствует или недействителен

Адаптер вашего типа должен быть более сложным. Входным адаптером типа является поток, а не элемент/поддерево. Вы можете загрузить следующее значение из потока, проанализировать его, а затем сделать именно то, что сделал десериализатор, что не имеет смысла, и вы можете просто использовать интерфейс десериализатора. Кроме того, вы можете прочитать поток, посмотреть, какие у него есть свойства, сохранить их в локальных переменных, пока не дойдете до свойства type (вы не можете предсказать его местоположение), затем закончите чтение оставшейся части свойств и создайте ваши конечные объекты Gold/Silver, основанные на типе, и все свойства, прочитанные и сохраненные.

Ответ 2

Хорошо, настоящий рабочий пример (на этот раз я уверен).

Клуб

package gson.test;
import java.util.ArrayList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Club {
    public static void main(String[] args) {
        // Setup a Club with 2 members
        Club myClub = new Club();
        myClub.addMember(new Silver("Jack"));
        myClub.addMember(new Gold("Jill"));
        myClub.addMember(new Silver("Mike"));

        // Get the GSON Object and register Type Adapter
        GsonBuilder builder = new GsonBuilder();
        builder.registerTypeAdapter(Member.class, new MemberDeserializer());
        builder.registerTypeAdapter(Member.class, new MemberSerializer());
        builder.setPrettyPrinting();
        Gson gson = builder.create();

        // Serialize Club to JSON
        String myJsonClub = gson.toJson(myClub); 

        // De-Serialize to Club
        Club myNewClub = gson.fromJson(myJsonClub, Club.class);
        System.out.println(myClub.equals(myNewClub) ? "Cloned!" : "Failed");
        System.out.println(gson.toJson(myNewClub));
    }

    private String title = "MyClub";
    private ArrayList<Member> members = new ArrayList<Member>();

    public boolean equals(Object club) {
        Club that = (Club) club;
        if (!this.title.equals(that.title)) return false;
        for (int i=0; i<this.members.size(); i++) {
            Member member1 = this.getMember(i);
            Member member2 = that.getMember(i);
            if (! member1.equals(member2)) return false;
        }
        return true;
    }
    public void addMember(Member newMember) { members.add(newMember); }
    public Member getMember(int i) { return members.get(i); }
}

Тематический класс участника

package gson.test;
public abstract class Member {
    private String clsname = this.getClass().getName() ;
    private int type;
    private String name = "unknown";

    public Member() { }
    public Member(String theName) {this.name = theName;}
    public int getType() { return type; }
    public void setType(int type) { this.type = type; }
    public boolean equals(Object member) {
        Member that = (Member) member;
        return this.name.equals(that.name);
    }
}

Бетонные субклассы Серебро и Золото

package gson.test;
public class Silver extends Member {
    private String silverData = "SomeSilverData";
    public Silver() { 
        super(); 
        this.setType(1); 
    }
    public Silver(String theName) {
        super(theName); 
        this.setType(1); 
    }
    public boolean equals(Object that) {
        Silver silver = (Silver)that;
        return (super.equals(that) && this.silverData.equals(silver.silverData)); 
    }
}

package gson.test;
public class Gold extends Member {
    private String goldData = "SomeGoldData";
    private String extraData = "Extra Gold Data";
    public Gold() {
        super(); 
        this.setType(2);
    }
    public Gold(String theName) { 
        super(theName); 
        this.setType(2); 
    }
    public boolean equals(Gold that) {
        Gold gold = (Gold) that;
        return (super.equals(that) && this.goldData.equals(gold.goldData)); 
    }
}

Пользовательский Member Serailizer

package gson.test;
import java.lang.reflect.Type;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class MemberSerializer implements JsonSerializer<Member> {

    public JsonElement serialize(Member src, Type member, JsonSerializationContext context) {
        switch (src.getType()) {
            case 1: return context.serialize((Silver)src);
            case 2: return context.serialize((Gold)src);
            default: return null;
        }
    }
}

Пользовательский десериализатор

package gson.test;
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;

public class MemberDeserializer implements JsonDeserializer<Member> {
    @Override
    public Member deserialize(JsonElement json, Type member, JsonDeserializationContext context) {
        int myType = json.getAsJsonObject().get("type").getAsInt();
        switch (myType) {
            case 1: return context.deserialize(json, Silver.class);
            case 2: return context.deserialize(json, Gold.class);
            default: return null;
        }
    }
}

И... вывод

Cloned!
{
  "title": "MyClub",
  "members": [
    {
      "silverData": "SomeSilverData",
      "clsname": "gson.test.Silver",
      "type": 1,
      "name": "Jack"
    },
    {
      "goldData": "SomeGoldData",
      "extraData": "Extra Gold Data",
      "clsname": "gson.test.Gold",
      "type": 2,
      "name": "Jill"
    },
    {
      "silverData": "SomeSilverData",
      "clsname": "gson.test.Silver",
      "type": 1,
      "name": "Mike"
    }
  ]
}

Я должен отметить, что мой реальный случай использования - это тот, где производительность не должна быть проблемой, я загружаю кеш объектов из текстовых файлов jSon, поэтому частота выполнения этого кода делает производительность гораздо менее важной, чем ремонтопригодность.