Gson 类型适配器与自定义解串器
Gson Type Adapter vs. Custom Deseralizer
下面的示例显示了一个 class(俱乐部),其中包含摘要 class(会员)的集合。我对是否需要 TypeAdapter 或 JsonDeserializer 来使反序列化正常工作感到困惑。序列化在没有任何帮助的情况下工作得很好,但反序列化会抛出异常。为了说明,我构建了以下 "clone" 测试。如果有人能展示一个工作示例,我将不胜感激。
第一俱乐部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); }
}
现在是抽象基地 Class 成员
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);}
}
以及会员(金牌和银牌)的两个具体子class
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.construct(ConstructorConstructor.java:107)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:186)
...
两者都可以。您选择哪一个实际上取决于潜在的性能影响,以及愿意编写多少代码。
反序列化器更贵。这是因为反序列化器的输入是一个 json 树,GSon 必须为与您的 class 匹配的元素创建一个完整的 JsonElement 子树,然后才能将其传递给您的反序列化器。如果您的模型有很多嵌套,则成本会增加。对于普通物体,它可以忽略不计。
看来你会根据将包含在目标对象中的type
属性 的值知道创建哪个class。您的解串器需要
- 查看传入的
JsonElement
对象,读取type
属性,判断类型
- 使用 class 和传递给您的相同元素调用
context.deserialize()
- 如果类型丢失或无效则抛出错误
您的类型适配器必须更复杂。类型适配器的输入是流,而不是 element/subtree。您可以完全从流中加载下一个值,解析它,然后完全按照反序列化器的方式执行,这没有意义,您可以改用反序列化器。或者,您可以读取流,查看有哪些属性,将它们保存到局部变量中,直到到达 type
属性 (您无法预测其位置),然后完成读取其余部分的属性,并根据类型创建最终的 Gold
/Silver
对象,并读取和保存所有属性。
好的,真实的工作示例(这次我很确定)。
俱乐部
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); }
}
会员摘要Class
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);
}
}
具体子Classes银和金
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));
}
}
自定义成员序列化程序
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 文本文件加载对象缓存,因此执行此代码的频率会提高性能远不如可维护性重要。
看起来 serializing/deserializing class 层次结构是一个常见问题。
甚至有一个 "official" 解决方案,在官方源代码库的 extras
目录中(不幸的是它不是 Maven 包的一部分)。
请检查:
下面的示例显示了一个 class(俱乐部),其中包含摘要 class(会员)的集合。我对是否需要 TypeAdapter 或 JsonDeserializer 来使反序列化正常工作感到困惑。序列化在没有任何帮助的情况下工作得很好,但反序列化会抛出异常。为了说明,我构建了以下 "clone" 测试。如果有人能展示一个工作示例,我将不胜感激。
第一俱乐部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); }
}
现在是抽象基地 Class 成员
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);}
}
以及会员(金牌和银牌)的两个具体子class
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.construct(ConstructorConstructor.java:107)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:186)
...
两者都可以。您选择哪一个实际上取决于潜在的性能影响,以及愿意编写多少代码。
反序列化器更贵。这是因为反序列化器的输入是一个 json 树,GSon 必须为与您的 class 匹配的元素创建一个完整的 JsonElement 子树,然后才能将其传递给您的反序列化器。如果您的模型有很多嵌套,则成本会增加。对于普通物体,它可以忽略不计。
看来你会根据将包含在目标对象中的type
属性 的值知道创建哪个class。您的解串器需要
- 查看传入的
JsonElement
对象,读取type
属性,判断类型 - 使用 class 和传递给您的相同元素调用
context.deserialize()
- 如果类型丢失或无效则抛出错误
您的类型适配器必须更复杂。类型适配器的输入是流,而不是 element/subtree。您可以完全从流中加载下一个值,解析它,然后完全按照反序列化器的方式执行,这没有意义,您可以改用反序列化器。或者,您可以读取流,查看有哪些属性,将它们保存到局部变量中,直到到达 type
属性 (您无法预测其位置),然后完成读取其余部分的属性,并根据类型创建最终的 Gold
/Silver
对象,并读取和保存所有属性。
好的,真实的工作示例(这次我很确定)。
俱乐部
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); }
}
会员摘要Class
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);
}
}
具体子Classes银和金
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));
}
}
自定义成员序列化程序
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 文本文件加载对象缓存,因此执行此代码的频率会提高性能远不如可维护性重要。
看起来 serializing/deserializing class 层次结构是一个常见问题。
甚至有一个 "official" 解决方案,在官方源代码库的 extras
目录中(不幸的是它不是 Maven 包的一部分)。
请检查: