Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GSON does not deserialize reference to outer class

On my Java application, I defined two classes, called A and B where B is inner class of A. Both are defined as serializable

public class A implements Serializable {

    int attrParent;
    List<B> items = new ArrayList<B>();

    public void setAttrParent(int attrParent) {
        this.attrParent = attrParent;
    }

    public int getAttrParent() {
        return attrParent;
    }

    public class B implements Serializable {

        private int attr;

        public void setAttr(int attr) {
            this.attr = attr;
        }

        public int getAttr() {
            return attr;
        }

        public int getSomeCalculationValue() {
            return this.attr * A.this.attrParent; // Problems occurs here
        }

    }
}

Before serializing this object with GSON, getSomeCalculationValue works fine. But, after serializing and deserializing, getSomeCalculationValue fails with a NullPointerException.

This happens because the inner class B doesn´t have a reference to outer class A anymore, so, A.this fails.

Does anybody knows how could I solve this, that is restoring the inner to outer reference while deserializing this object?

like image 960
regisxp Avatar asked Oct 18 '13 12:10

regisxp


1 Answers

As Gson documentation says:

Gson can serialize static nested classes quite easily.

Gson can also deserialize static nested classes. However, Gson can not automatically deserialize the pure inner classes since their no-args constructor also need a reference to the containing Object which is not available at the time of deserialization. You can address this problem by either making the inner class static or by providing a custom InstanceCreator for it.

Changing B to a static inner class is not possible since your method needs a reference to the outer class in getSomeCalculationValue, so, I've tried to solve your problem with an InstanceCreator but solution was a bit ugly, so I propose you to use a custom deserialized. I changed your A class a little, making items public to make easier to create the example I show you.

public class ADeserializer implements JsonDeserializer<A> {
    public A deserialize(JsonElement json, Type typeOfT,
        JsonDeserializationContext context) throws JsonParseException {

    A a = new A();
    a.attrParent = json.getAsJsonObject().get("attrParent").getAsInt();
    JsonArray ja = json.getAsJsonObject().get("items").getAsJsonArray();
    for(JsonElement e: ja){
        B b = a.new B();
        b.setAttr(e.getAsJsonObject().get("attr").getAsInt());
        a.items.add(b);
    }
    return a;
    }

}

And this is how I use it:

public class Q19449761 {

    public static void main(String[] args) {

        A a = new A();
        a.setAttrParent(3);
        B b = a.new B();
        b.setAttr(10);
        a.items.add(b);
        System.out.println("Before serializing: "  + a.items.get(0).getSomeCalculationValue());

        Gson g = new Gson();

        String json = g.toJson(a, A.class);
        System.out.println("JSON string: " + json);

        A test2 = g.fromJson(json, A.class);

        try {
            System.out.println("After standard deserialization: " +test2.items.get(0).getSomeCalculationValue());
        } catch (Exception e) {
            e.printStackTrace();
        }

        GsonBuilder builder = new GsonBuilder();
        builder.registerTypeAdapter(A.class, new ADeserializer());

        A test3 = builder.create().fromJson(json, A.class);

        System.out.println("After custom deserialization: " + test3.items.get(0).getSomeCalculationValue());

    }

}

And this is my execution:

Before serializing: 30
JSON string: {"attrParent":3,"items":[{"attr":10}]}
java.lang.NullPointerException
    at stackoverflow.questions.q19449761.A$B.getSomeCalculationValue(A.java:32)
    at stackoverflow.questions.q19449761.Q19449761.main(Q19449761.java:26)
After custom deserialization: 30

Two final notes:

  1. You do not need to implement Serializable interface, JSON has nothing in common with Java serialization
  2. My deserializer lacks of null cases management, you should complete for null JSON or null fields.
like image 51
giampaolo Avatar answered Nov 05 '22 12:11

giampaolo