Gson - Setting object reference on-the-fly using InstanceCreator
Asked Answered
H

2

4

I'm stuck in a problem where I need to set reference of my object being parsed to its child objects during deserialization using Gson and InstanceCreator.

To depict the problem, following is the simple representation of classes' structure.

public class Workshift {
    private final transient Context context;
    private final Visit visit;

    public Workshift(Context context) {
        this.context = context;
        this.visit = new Visit(this);
    }
}

public class Visit {
    private final transient Workshift workshift;

    public Visit(Workshift ws) {
        this.workshift = ws;
    }
}

With this structure, I am able to set Context in Workshift by providing an InstanceCreator to my GsonBuilder, for example:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Workshift.class, new InstanceCreator<Workshift>() {
        @Override
        public Workshift createInstance(Type type) {
            return new Workshift(context);
        }
    })
    .create();

I know, I can add additional InstanceCreator to my GsonBuilder, but I'm not sure how to provide a reference of my Workshift object which is in the process of being parsed (on-the-fly) to Visit object?

Any help would be appreciated!

Hyaline answered 27/2, 2014 at 14:33 Comment(5)
waqas, if you add java tag in your question then you will get fast reply.Audie
As far as i understand, with the code you've posted, the InstanceCreator invokes Workshift(Context) that already instantiates a Visit. So, i don't understand where the problem is. Do you want to modify this structure ??Mccully
@AntoineMarques I want the gson-instantiated Workshift object to set its own reference inside its visit object. Currently, the workshift field inside Visit turns out to be null when deserialized.Hyaline
You'll have to remove 'final' modifier of field Workshift.visit and either : - make Visit a nested class of Workshift - change Workshift.visit visibility to default and have the two classes be in the same package.Mccully
I think, removing the modifier and changing the visibility will not help as I need to pass workshift reference to visit while deserializing. They are in the same package.Hyaline
R
3

You should definitely use GraphAdapterBuilder.

As you said in the comment under @Braj 's answer,

workshift is set transient for a reason so that it won't serialize this object when serializing visit object. If its not marked as transient then the serialization falls into stack overflow exception - by creating an unstoppable loop

This has a simple solution.

Workshift.java

public class Workshift {
    private final transient Context context;
    private final Visit visit;
    //for testing
    private String workshift_description;

    public Workshift(Context context,String id) {
        this.workshift_description=id;
        this.context = context;
        this.visit = new Visit(this);

    }
    public String getId() {
        return workshift_description;
    }

    public void setId(String id) {
        this.workshift_description = id;
    }
    public String toString() {
        return "[Workshift element => { WD: "+this.workshift_description+", VD : "+this.visit.getVisit_description()+"}";
    }
}

Visit.java

public class Visit {

    private final /* transient  */ Workshift workshift;

    public Visit(Workshift ws) {
        this.workshift = ws;

    }
    public String getVisit_description() {
        return "visit containing  "+ workshift.getId();
    }

}

The trick resides here:

GsonBuilder gsonBuilder = new GsonBuilder();
        new GraphAdapterBuilder()
        .addType(Visit.class)
        .addType(Workshift.class)
        .registerOn(gsonBuilder);

Putting all together,

public static void main(String[] args) {

        Workshift[] workshifts = new Workshift[10];
        for (int i = 0; i < workshifts.length; i++) {
            //Replace Context(i) for the real one
            workshifts[i] = new Workshift(new Context(i), "Workshift#"
                    + i);
        }
        System.out.println("Original Workshifts array:");
        for (int i = 0; i < workshifts.length; i++) {
            System.out.println(workshifts[i]);
        }
        System.out.println("===================================");

        GsonBuilder gsonBuilder = new GsonBuilder();
        new GraphAdapterBuilder()
        .addType(Visit.class)
        .addType(Workshift.class)
        .registerOn(gsonBuilder);

        Gson gson = gsonBuilder.setPrettyPrinting().create();
        String serialized = gson.toJson(workshifts);
        // System.out.println(serialized);
        Workshift[] w_array = gson.fromJson(serialized, Workshift[].class);
        // System.out.println(gson.toJson(w_array));

        System.out.println("Des-serialized Workshifts array:");
        for (int i = 0; i < w_array.length; i++) {
            System.out.println(w_array[i]);
        }
        System.out.println("===================================");

Output:

Original Workshifts array:
[Workshift element => { WD: Workshift#0, VD : visit containing  Workshift#0}
[Workshift element => { WD: Workshift#1, VD : visit containing  Workshift#1}
[Workshift element => { WD: Workshift#2, VD : visit containing  Workshift#2}
[Workshift element => { WD: Workshift#3, VD : visit containing  Workshift#3}
[Workshift element => { WD: Workshift#4, VD : visit containing  Workshift#4}
[Workshift element => { WD: Workshift#5, VD : visit containing  Workshift#5}
[Workshift element => { WD: Workshift#6, VD : visit containing  Workshift#6}
[Workshift element => { WD: Workshift#7, VD : visit containing  Workshift#7}
[Workshift element => { WD: Workshift#8, VD : visit containing  Workshift#8}
[Workshift element => { WD: Workshift#9, VD : visit containing  Workshift#9}
===================================
Des-serialized Workshifts array:
[Workshift element => { WD: Workshift#0, VD : visit containing  Workshift#0}
[Workshift element => { WD: Workshift#1, VD : visit containing  Workshift#1}
[Workshift element => { WD: Workshift#2, VD : visit containing  Workshift#2}
[Workshift element => { WD: Workshift#3, VD : visit containing  Workshift#3}
[Workshift element => { WD: Workshift#4, VD : visit containing  Workshift#4}
[Workshift element => { WD: Workshift#5, VD : visit containing  Workshift#5}
[Workshift element => { WD: Workshift#6, VD : visit containing  Workshift#6}
[Workshift element => { WD: Workshift#7, VD : visit containing  Workshift#7}
[Workshift element => { WD: Workshift#8, VD : visit containing  Workshift#8}
[Workshift element => { WD: Workshift#9, VD : visit containing  Workshift#9}
===================================

There's no StackOverflow error.

if you un-comment the line

// System.out.println(serialized);

The output would be like this:

[
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#0"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#1"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#2"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#3"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#4"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#5"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#6"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#7"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#8"
    },
    "0x2": {
      "workshift": "0x1"
    }
  },
  {
    "0x1": {
      "visit": "0x2",
      "workshift_description": "Workshift#9"
    },
    "0x2": {
      "workshift": "0x1"
    }
  }

]

That's because Gson is replacing your references, in order to avoid that stack overflow exception. It's like emulating pointers

Hope it helps.

Note: Remember to copy the files GraphAdapterBuilder.java and change the line

private final ConstructorConstructor constructorConstructor = new ConstructorConstructor();

with

private final ConstructorConstructor constructorConstructor = new ConstructorConstructor(instanceCreators);

It won't compile otherwise. Maybe it's fixed right now.

Rimola answered 8/3, 2014 at 15:18 Comment(10)
Well, that looks interesting. I'll test this on Monday once I'm back on my computer at work. Thanks for the idea.Hyaline
I've tried your suggestion but strangely I'm getting com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Unexpected recursive call to read() for 0x1 with a huge stack trace.Hyaline
@Hyaline Have you tried with the json I've posted?Rimola
I've used your solution in my project which has a little more complex class structure than this question.Hyaline
@Hyaline try to download the latest code from google-gson.googlecode.com/svn/trunkRimola
I think I've got the idea of what you are trying to show using GraphAdapterBuilder and its a good one. But since I'm using a lot of Interfaces, custom TypeAdapters and InstanceCreators, it makes it difficult for me to implement in my current project. Perhaps, I'll try it some other time with some simple objects. Anyhow, I think your answer is more closer to what I was looking for. Thanks for the help.Hyaline
@Hyaline Glad to help. Anyway, if you can share a little bit more of code, not the content of it, only the classes/interfaces structure, I can be more helpful.Rimola
why the code or dependency is not available on maven? cannot find the classes while using the lastes gson dependencySilverweed
@Rimola can you help getting the dependency? @@RimolaSilverweed
@PascoalEddyBayonne probablu you can grab it via maven gson extras mvnrepository.com/artifact/com.google.code.gson/gson-extras (github.com/google/gson/tree/master/extras/src/main/java/com/…)Rimola
L
0

Problem:

Currently, the workshift field inside Visit turns out to be null when deserialized.

Solution:

workshift field is a transient member in Visit class and transient members won't be serialized that's why you are getting null value when deserialized.

To solve this problem you have to set the reference of workshift in the visit class manually via calling its setter method after getting workshift object when deserialized.

when deserialized you have reference of both the object workshift and visit. Just need to pass the reference of workshift to visit will solve it.

Visit.java:

public class Visit {
    private final transient Workshift workshift;

    public Visit() {

    }

    public Workshift getWorkshift() {
        return workshift;
    }

    public void setWorkshift(Workshift workshift) {
        this.workshift = workshift;
    }

}

Use JsonDeserializer to set the reference of workshift into visit class.

Sample Code:

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Type;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

    public class GSON {

        /**
         * @param args
         * @throws IOException
         * @throws ClassNotFoundException
         */
        public static void main(String[] args) throws Exception {
            // serialize
            Gson gson = new Gson();
            String json = gson.toJson(new Workshift());
            System.out.println("Workshift JSON:" + json);

            // deserialize
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(Workshift.class, new WorkshiftDeserializer());

            Workshift workshift = builder.create().fromJson(json, Workshift.class);
            System.out.println("Reference of Workshift from Visit:"
                    + workshift.getVisit().getWorkshift());

        }

    }

    class Workshift implements Serializable {
        private Visit visit;

        public Workshift() {
            this.visit = new Visit(this);
        }

        public Visit getVisit() {
            return visit;
        }

        public void setVisit(Visit visit) {
            this.visit = visit;
        }

    }

    class Visit implements Serializable {
        private transient Workshift workshift;

        public Visit() {

        }

        public Visit(Workshift ws) {
            this.workshift = ws;
        }

        public Workshift getWorkshift() {
            return workshift;
        }

        public void setWorkshift(Workshift workshift) {
            this.workshift = workshift;
        }

    }


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

            Gson gson = new Gson();

            Workshift workshift = gson.fromJson(json, Workshift.class);
            workshift.getVisit().setWorkshift(workshift);
            return workshift;
        }
    }
Lenin answered 6/3, 2014 at 23:17 Comment(7)
workshift is set transient for a reason so that it won't serialize this object when serializing visit object. If its not marked as transient then the serialization falls into stack overflow exception - by creating an unstoppable loop.Hyaline
If its not serialized then how can you expect it when deserialized.Lenin
Workshift is serialized but not inside visit. Visit is a child of Workshift, so there's no need to serialize it again inside Visit. Visit only holds reference to workshift. This example is just a structural representation of problem I'm having. Otherwise my actual data structure is far more complex than this.Hyaline
The problem is when calling workshift.getVisit().setWorkshift(workshift) in WorkshiftDeserializer the getVisits() returns null. Visits are not deserialized during deserializing workshift.Hyaline
Its working perfectly for me. Let me share my class you again and try it. I have updated my sample code. Here is the output: Workshift JSON:{"visit":{}} Reference of Workshift from Visit:com.test.Workshift@4e82701e. If problem still exists then share me the json string generated during serialization.Lenin
by this way, I'm loosing all the data my workshift object contains in WorkshiftDeserializer because we are calling Workshift workshift = new Workshift(); and it does not fill-in the data from json string. It simply create a new object, rather than restoring an old one.Hyaline
Let me create it from JSON string in WorkshiftDeserializer. I have updated my answer. Please try it.Lenin

© 2022 - 2024 — McMap. All rights reserved.