Gson Type Adapter vs. Custom Deseralizer












12















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









share|improve this question


















  • 1





    equals() method must take Object parameter type, not Club

    – Pawel Veselov
    Jun 3 '15 at 21:24











  • Thanks, it would have taken me a bit to catch that.

    – Mike Storey
    Jun 3 '15 at 22:21











  • Got it, thanks, I will up-vote next time.

    – Mike Storey
    Jun 3 '15 at 22:35


















12















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









share|improve this question


















  • 1





    equals() method must take Object parameter type, not Club

    – Pawel Veselov
    Jun 3 '15 at 21:24











  • Thanks, it would have taken me a bit to catch that.

    – Mike Storey
    Jun 3 '15 at 22:21











  • Got it, thanks, I will up-vote next time.

    – Mike Storey
    Jun 3 '15 at 22:35
















12












12








12


5






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









share|improve this question














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






java gson






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Jun 3 '15 at 21:22









Mike StoreyMike Storey

3913619




3913619








  • 1





    equals() method must take Object parameter type, not Club

    – Pawel Veselov
    Jun 3 '15 at 21:24











  • Thanks, it would have taken me a bit to catch that.

    – Mike Storey
    Jun 3 '15 at 22:21











  • Got it, thanks, I will up-vote next time.

    – Mike Storey
    Jun 3 '15 at 22:35
















  • 1





    equals() method must take Object parameter type, not Club

    – Pawel Veselov
    Jun 3 '15 at 21:24











  • Thanks, it would have taken me a bit to catch that.

    – Mike Storey
    Jun 3 '15 at 22:21











  • Got it, thanks, I will up-vote next time.

    – Mike Storey
    Jun 3 '15 at 22:35










1




1





equals() method must take Object parameter type, not Club

– Pawel Veselov
Jun 3 '15 at 21:24





equals() method must take Object parameter type, not Club

– Pawel Veselov
Jun 3 '15 at 21:24













Thanks, it would have taken me a bit to catch that.

– Mike Storey
Jun 3 '15 at 22:21





Thanks, it would have taken me a bit to catch that.

– Mike Storey
Jun 3 '15 at 22:21













Got it, thanks, I will up-vote next time.

– Mike Storey
Jun 3 '15 at 22:35







Got it, thanks, I will up-vote next time.

– Mike Storey
Jun 3 '15 at 22:35














3 Answers
3






active

oldest

votes


















15














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.






share|improve this answer
























  • I rushed through testing, my serialization is not working as I thought it was, so it looks like I also need a special serializer to dive into the subtypes of the collection. I will continue to test and post a final working example when I have one.

    – Mike Storey
    Jun 3 '15 at 23:22



















10














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.






share|improve this answer

































    0














    It looks like serializing/deserializing class hierarchies is a common problem.



    There is even an "official" solution, inside extras directory of the official source repo (unfortunately it is not part of the Maven package though).



    Please check:




    • The explanation: https://blog.novatec-gmbh.de/gson-object-hierarchies/

    • The solution: https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java. It is suggested to just copy/paste the source.






    share|improve this answer























      Your Answer






      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "1"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: true,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f30631004%2fgson-type-adapter-vs-custom-deseralizer%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      3 Answers
      3






      active

      oldest

      votes








      3 Answers
      3






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      15














      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.






      share|improve this answer
























      • I rushed through testing, my serialization is not working as I thought it was, so it looks like I also need a special serializer to dive into the subtypes of the collection. I will continue to test and post a final working example when I have one.

        – Mike Storey
        Jun 3 '15 at 23:22
















      15














      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.






      share|improve this answer
























      • I rushed through testing, my serialization is not working as I thought it was, so it looks like I also need a special serializer to dive into the subtypes of the collection. I will continue to test and post a final working example when I have one.

        – Mike Storey
        Jun 3 '15 at 23:22














      15












      15








      15







      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.






      share|improve this answer













      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.







      share|improve this answer












      share|improve this answer



      share|improve this answer










      answered Jun 3 '15 at 21:40









      Pawel VeselovPawel Veselov

      2,16432345




      2,16432345













      • I rushed through testing, my serialization is not working as I thought it was, so it looks like I also need a special serializer to dive into the subtypes of the collection. I will continue to test and post a final working example when I have one.

        – Mike Storey
        Jun 3 '15 at 23:22



















      • I rushed through testing, my serialization is not working as I thought it was, so it looks like I also need a special serializer to dive into the subtypes of the collection. I will continue to test and post a final working example when I have one.

        – Mike Storey
        Jun 3 '15 at 23:22

















      I rushed through testing, my serialization is not working as I thought it was, so it looks like I also need a special serializer to dive into the subtypes of the collection. I will continue to test and post a final working example when I have one.

      – Mike Storey
      Jun 3 '15 at 23:22





      I rushed through testing, my serialization is not working as I thought it was, so it looks like I also need a special serializer to dive into the subtypes of the collection. I will continue to test and post a final working example when I have one.

      – Mike Storey
      Jun 3 '15 at 23:22













      10














      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.






      share|improve this answer






























        10














        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.






        share|improve this answer




























          10












          10








          10







          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.






          share|improve this answer















          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.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Jun 4 '15 at 0:14

























          answered Jun 4 '15 at 0:07









          Mike StoreyMike Storey

          3913619




          3913619























              0














              It looks like serializing/deserializing class hierarchies is a common problem.



              There is even an "official" solution, inside extras directory of the official source repo (unfortunately it is not part of the Maven package though).



              Please check:




              • The explanation: https://blog.novatec-gmbh.de/gson-object-hierarchies/

              • The solution: https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java. It is suggested to just copy/paste the source.






              share|improve this answer




























                0














                It looks like serializing/deserializing class hierarchies is a common problem.



                There is even an "official" solution, inside extras directory of the official source repo (unfortunately it is not part of the Maven package though).



                Please check:




                • The explanation: https://blog.novatec-gmbh.de/gson-object-hierarchies/

                • The solution: https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java. It is suggested to just copy/paste the source.






                share|improve this answer


























                  0












                  0








                  0







                  It looks like serializing/deserializing class hierarchies is a common problem.



                  There is even an "official" solution, inside extras directory of the official source repo (unfortunately it is not part of the Maven package though).



                  Please check:




                  • The explanation: https://blog.novatec-gmbh.de/gson-object-hierarchies/

                  • The solution: https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java. It is suggested to just copy/paste the source.






                  share|improve this answer













                  It looks like serializing/deserializing class hierarchies is a common problem.



                  There is even an "official" solution, inside extras directory of the official source repo (unfortunately it is not part of the Maven package though).



                  Please check:




                  • The explanation: https://blog.novatec-gmbh.de/gson-object-hierarchies/

                  • The solution: https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java. It is suggested to just copy/paste the source.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 26 '18 at 5:31









                  Nikola MihajlovićNikola Mihajlović

                  507310




                  507310






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Stack Overflow!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f30631004%2fgson-type-adapter-vs-custom-deseralizer%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Contact image not getting when fetch all contact list from iPhone by CNContact

                      count number of partitions of a set with n elements into k subsets

                      A CLEAN and SIMPLE way to add appendices to Table of Contents and bookmarks