Solved Gson Serialization and Deseralization

Discussion in 'Spigot Plugin Development' started by Torciv, Jul 13, 2018.

  1. This is the schem of my classes:

    Code (Java):
    public class Main extends JavaPlugin {

        @Override
        public void onEnable() {

            B bb = new B("holab", 4);
            SB sb = new SB(1, bb);
            sb.setExtract(new Extract(sb,1));

            String ser1 = gson.toJson(sb);
            System.out.println(ser1);

            SB b1 = gson.fromJson(ser1, SB.class);

            System.out.println(b1.toString());

        }

    Code (Java):
    public abstract class A {

        protected String name;

        public A(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "A{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    Code (Java):
    public class B extends A{

        private int val;

        public B(String name, int val) {
            super(name);
            this.val = val;
        }


        public int getVal() {
            return val;
        }

        public void setVal(int val) {
            this.val = val;
        }


        @Override
        public String toString() {
            return super.toString() + "B{" +
                    "val=" + val +
                    '}';
        }
    }
     

    Code (Java):
    public class SB {

        private int id;
        private B b;
        private Extract extract;

        public SB(int id, B b) {
            this.id = id;
            this.b = b;
        }

        public SB(int id, B b, Extract extract) {
            this.id = id;
            this.b = b;
            this.extract = extract;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public B getB() {
            return b;
        }

        public void setB(B b) {
            this.b = b;
        }

        public Extract getExtract() {
            return extract;
        }

        public void setExtract(Extract extract) {
            this.extract = extract;
        }

        @Override
        public String toString() {
            return "SB{" +
                    "id=" + id +
                    ", b=" + b +
                    ", extract=" + extract +
                    '}';
        }

    Code (Java):
    public class Extract {

        private transient SB sb;
        private int value;

        public Extract(SB sb, int value) {
            this.sb = sb;
            this.value = value;
        }

        public Extract(int value) {
            this.value = value;
        }

        public SB getSb() {
            return sb;
        }

        public void setSb(SB sb) {
            this.sb = sb;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }

        public void doSomething(){
            System.out.println("This is the ID: " + this.sb.getId());
        }

        @Override
        public String toString() {
            return "Extract{" +
                    "sb=" + sb +
                    ", value=" + value +
                    '}';
        }

    So well the output is this:
    Code (Text):

    [16:37:49 INFO]: {"id":1,"b":{"val":4,"name":"holab"},"extract":{"value":1}}
    [16:37:49 INFO]: SB{id=1, b=A{name='holab'}B{val=4}, extract=Extract{sb=null, value=1}}
     
    • As you can see Extract sb is null. How can I make it not to be null?
    This is just a test to learn thats why this weird classes. Also, would you store in SB class the A class or the B class?
    Cause I do not really know what is better:
    • B bb = new B("holab", 4);
    • A bb = new B("holab", 4); // Then I will have to use instanceof
    Is it actually the same right? The only thing that changes is that I would have to do an instanceof. The fact is that I actually know that all the objects for SB would be B, thats why I decided to store them as B directly. Not sure if that is a good practice.

    Also one thing I noticed is that If I change it in SB class from B to A I get this error and It doesn't matter if bb was either:
    A bb = new B("holab", 4);
    B bb = new B("holab", 4);

    Code (Text):
    [16:43:55 ERROR]: Error occurred while enabling TSerialize v1.0 (Is it up to date?)
    java.lang.RuntimeException: Unable to invoke no-args constructor for class io.github.victorml11.otros.A. Register an InstanceCreator with Gson for this type may fix this problem.
            at com.google.gson.internal.ConstructorConstructor$12.construct(ConstructorConstructor.java:210) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:162) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.Gson.fromJson(Gson.java:803) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.Gson.fromJson(Gson.java:768) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.Gson.fromJson(Gson.java:717) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.Gson.fromJson(Gson.java:689) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at io.github.victorml11.Main.onEnable(Main.java:47) ~[?:?]
            at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:321) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:332) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:404) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.loadPlugin(CraftServer.java:313) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.enablePlugins(CraftServer.java:272) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.reload(CraftServer.java:726) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.Bukkit.reload(Bukkit.java:556) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.command.defaults.ReloadCommand.execute(ReloadCommand.java:25) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:143) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.dispatchCommand(CraftServer.java:619) [fluxJar.jar:FluxSpigot-7f01f407]
            at org.bukkit.craftbukkit.v1_8_R3.CraftServer.dispatchServerCommand(CraftServer.java:582) [fluxJar.jar:FluxSpigot-7f01f407]
            at net.minecraft.server.v1_8_R3.DedicatedServer.aO(DedicatedServer.java:416) [fluxJar.jar:FluxSpigot-7f01f407]
            at net.minecraft.server.v1_8_R3.DedicatedServer.B(DedicatedServer.java:379) [fluxJar.jar:FluxSpigot-7f01f407]
            at net.minecraft.server.v1_8_R3.MinecraftServer.A(MinecraftServer.java:715) [fluxJar.jar:FluxSpigot-7f01f407]
            at net.minecraft.server.v1_8_R3.MinecraftServer.run(MinecraftServer.java:618) [fluxJar.jar:FluxSpigot-7f01f407]
            at java.lang.Thread.run(Unknown Source) [?:1.8.0_161]
    Caused by: java.lang.reflect.InvocationTargetException
            at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source) ~[?:?]
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_161]
            at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_161]
            at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:48) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.internal.ConstructorConstructor$12.construct(ConstructorConstructor.java:207) ~[fluxJar.jar:FluxSpigot-7f01f407]
            ... 24 more
    Caused by: java.lang.InstantiationException: io.github.victorml11.otros.A
            at sun.misc.Unsafe.allocateInstance(Native Method) ~[?:1.8.0_161]
            at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source) ~[?:?]
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_161]
            at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_161]
            at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:48) ~[fluxJar.jar:FluxSpigot-7f01f407]
            at com.google.gson.internal.ConstructorConstructor$12.construct(ConstructorConstructor.java:207) ~[fluxJar.jar:FluxSpigot-7f01f407]
            ... 24 more

    So basically my questions are:
    • How can I make SB not to be null in Extract class after we deserialize?
    • What is better: Store in SB an A or B? - I actually know all objects of SB would be B.
    • In case I decide to store as A in SB how can I solve the above error?

    Thanks.
     
  2. That seems interesting, i will have to implement a custom serialization for SB in orther to solve that.

    And what about saving it as A in SB? That error is kinda weird
     
  3. If you use details of B (this seems to be the case, because you said that you would have to use instanceof when using the object), then sure go ahead and use B as field type.
    In other cases, for example if you only use it as A and you don't need to know whether its actually an A or B during usage (for example if A is an interface), then go ahead and declare the field as A.

    I haven't looked into the details of how things work in gson, but from what I got / assume so far:
    If you declare the field as A, gson might not be able to know what exact type of object it should create for that field during deserialization (I think gson might use the field types for that information). So to solve this, you might have to use a custom deserializer for SB and there construct the correct type of A object (B in your case).
    I think you can also register a 'type hierarchy adapter' for A that will always default to creating an instance of B when an A is requested. Just like gson will default to create a LinkedHashSet when the field type is a Set, or an ArrayList if the field type is List or Collection.

    An other alternative might be to store additional type information during serialization and then have a deserializer that creates the corresponding object during deserialization. There seems to even be a gson-addon that does something like this: https://github.com/google/gson/blob...n/typeadapters/RuntimeTypeAdapterFactory.java

    Regarding the error you get above when declaring the field as A: Gson will first attempt to create an A object, which it seems to be not be able to do for A. Gson attempts some trickery if your class does not provide a non-args constructor (see here: https://stackoverflow.com/questions/18645050/is-default-no-args-constructor-mandatory-for-gson), and for some reason this works for B dut doesn't for A (maybe because A is abstract and B is not ..). The safe solution would be to always declare a non-args constructor or register an InstanceCreator as the exception suggests.
     
    #4 blablubbabc, Jul 13, 2018
    Last edited: Jul 13, 2018
    • Informative Informative x 1
  4. Wow really thank you.
    I will work on it. (y) Thanks for you help, really appreciate it.
     
  5. God, actually noticed that this causes and stack overflow:

    Code (Text):

    sb2 = new SB(1, bb);
    sb2.setExtract(new Extract(sb2,1));
    System.out.println(sb2.toString());
    Is this because infinite printing:
    Sb -> Extract -> Sb -> Extract?

    Does this set correctly the sb2 instance into extract? Or is it sb2 still null?
     
    #6 Torciv, Jul 14, 2018
    Last edited: Jul 14, 2018
  6. Yes, according to your code above, calling the setter sets the value of the field.

    Regarding the infinite loop of sb2.toString(): Maybe consider only printing the SB#id inside Extract#toString.
     
    • Friendly Friendly x 1
  7. Yes. It was that. Thank you so much. I ended up making it:
    I will leave here my custom adapter just in case someone has the same doubts:

    Code (Java):
    public class SBTypeAdapter extends TypeAdapter<SB> {


        @Override
        public void write(JsonWriter writer, SB sb) throws IOException {
            writer.beginObject();
            writer.name("id").value(sb.getId());
            writer.name("b").beginObject();
            writer.name("val").value(sb.getB().getVal());
            writer.name("name").value(sb.getB().getName());
            writer.endObject();
            writer.name("extract").beginObject();
            writer.name("value").value(sb.getExtract().getValue());
            writer.endObject();
            writer.endObject();
        }

        @Override
        public SB read(JsonReader reader) throws IOException {
            reader.beginObject();
            int id = -1;
            int bval = -1;
            String bname = "";
            int eval = -1;
            Extract extract = null;
            B bb = null;

            while(reader.hasNext()){
                switch (reader.nextName()){
                    case "id":
                        id = reader.nextInt();
                        break;
                    case "b":
                        reader.beginObject();
                        while(reader.hasNext()){
                            switch (reader.nextName()){
                                case "val":
                                    bval = reader.nextInt();
                                    break;
                                case "name":
                                    bname = reader.nextString();
                                    break;
                            }
                            bb = new B(bname, bval);
                        }
                        reader.endObject();
                        break;
                    case "extract":
                        reader.beginObject();
                        while(reader.hasNext()){
                            switch (reader.nextName()){
                                case "value":
                                    eval = reader.nextInt();
                                    break;
                            }
                            extract = new Extract(eval);
                        }
                        reader.endObject();
                        break;
                }
            }
            reader.endObject();

            SB sb = new SB(id,bb,extract);
            sb.getExtract().setSb(sb);
            return sb;

        }

    I am sure it can be done with https://stackoverflow.com/questions...-object-while-deserializing-a-json-using-gson
    But I found easier right know to use reader and writer.

    Code (Text):
    {
      "id": 100,
      "b": {
        "val": 4,
        "name": "holab"
      },
      "extract": {
        "value": 1
      }
    }

    Thanks for your help @blablubbabc
     
    • Friendly Friendly x 1

Share This Page