Gson Type Adapter与Custom Deseralizer

时间:2015-06-03 21:22:52

标签: java gson

下面的示例显示了一个包含抽象类(Member)集合的类(Club)。我很困惑我是否需要TypeAdapter或JsonDeserializer来使反序列化正常工作。序列化在没有任何帮助的情况下工作正常,但反序列化会抛出异常。为了说明我已经构建了以下“克隆”测试。如果有人能展示一个有效的例子,我将非常感激。

First Club Class

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)
...

3 个答案:

答案 0 :(得分:16)

You can do both. Which one you pick depends really on potential performance impact, and how much code are willing to write.

Deserializers are more expensive. That is because the input to deserializer is a json tree, and GSon will have to create a full JsonElement subtree of the property that matches your class, before it can pass it to your deserializer. If your classes have a lot of nesting, that cost increases. For plain objects, it will be negligible.

It seems that you will know which class to create based on the value of type property that will be included in target object. Your deserializer will need to

  • look into the passed JsonElement object, read the type property, determine the type
  • call context.deserialize() with the class and the same element that was passed to you
  • throw an error if type was missing or invalid

Your type adapter will have to be more complex. The input to the type adapter is stream, not an element/subtree. You can load the next value entirely from the stream, parse it, and then do exactly what deserializer did, which doesn't make sense and you can just use the deserializer interface. Alternatively, you can read the stream, see what properties there are, save them into local variables, until you get to the type property (you can't predict its location), then finish reading the remainder of the properties, and create your final Gold/Silver objects based on type, and all the properties read and saved.

答案 1 :(得分:11)

好的,真正有效的例子(我现在很确定)。

俱乐部

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

Custom 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文本文件加载对象缓存,因此执行此代码的频率使得性能远不如可维护性。

答案 2 :(得分:0)

序列化/反序列化类层次结构似乎是一个常见问题。

在官方源回购的extras目录中甚至还有一个“官方”解决方案(不幸的是,它不是Maven软件包的一部分)。

请检查: