The example below shows a class (Club) that contains a collection of an abstract class (Member). I'm confused as to whether I need a TypeAdapter or JsonDeserializer to make the Deserialization work correctly. Serialization works just fine without any help, but Deserialization is throwing exceptions. To illustrate I've built the following "clone" test. If anyone could show a working example I would be very grateful.
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); }
}
Now the Abstract Base Class Member
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);}
}
And two concrete sub-classes of Member (Gold and Silver)
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));
}
}
And finally the output
{"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)
...
Gson is first constructed using GsonBuilder and then, toJson(Object) or fromJson(String, Class) methods are used to read/write JSON constructs.
To use a custom type adapter with Gson, you must register it with a GsonBuilder : GsonBuilder builder = new GsonBuilder(); builder. registerTypeAdapter(Point. class, new PointAdapter()); // if PointAdapter didn't check for nulls in its read/write methods, you should instead use // builder.
Introduction. Gson is the main actor class of Google Gson library. It provides functionalities to convert Java objects to matching JSON constructs and vice versa. Gson is first constructed using GsonBuilder and then toJson(Object) or fromJson(String, Class) methods are used to read/write JSON constructs.
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 for the element that matches your class, before it can pass it to your deserializer. If your model has 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
JsonElement
object, read the type
property, determine the typecontext.deserialize()
with the class and the same element that was passed to youYour type adapter will have to be more complex. The input to the type adapter is a 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 instead. 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.
Ok, real working example (I'm pretty sure this time).
The Club
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); }
}
The Member Abstract 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);
}
}
The Concrete Sub-Classes Silver and Gold
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));
}
}
The 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;
}
}
}
The custom Deserializer
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;
}
}
}
And... the output
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"
}
]
}
I should note that my real use-case is one where performance should not be an issue, I'm loading a cache of objects from jSon text files so the frequency with this code is executed makes performance much less important than maintainability.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With