Home > database >  GsonBuilder - Get Object from Interface
GsonBuilder - Get Object from Interface

Time:03-20

I have a GsonFactory declared like this:

public class GsonFactory {
    public Gson create() {
        return new GsonBuilder()
                .setPrettyPrinting()
                .registerTypeAdapter(MyInterface.class, new MyInstanceCreator())
                .create();
    }

    private static final GsonFactory instance = new GsonFactory();

    private GsonFactory() {
    }

    public static Gson getInstance(){
        return instance.create();
    }
}

I also have the InstanceCreator class:

public class MyInstanceCreator implements InstanceCreator<MyInterface> {
    @Override
    public StrategySymbol createInstance(Type type) {
        return new MyInterfaceImpl();
    }
}

class MyClass {
  private List<MyInterface> myList;

  public List<MyInterface> getMyList() { return myList; }

  public void setMyList(List<MyInterface> myList) { this.myList = myList; }
}

interface MyInterface {
  Long getValue1();
  void setValue1(Long value1);
}

class MyInterfaceImpl implements MyInterface {
  private Long value1;

  @Override
  public Long getValue1() {
     return value1;
  }

  @Override
  public void setValue1(Long value1) {
     this.value1 = value1
  }
}

This code seems well implemented, but if I try to parse a JSON with value1:

MyClass obj = GsonFactory.getInstance().fromJson("{'myList': [{'value1':8}]}", MyClass.class);

The Object is returned with an instance of MyInterfaceImpl, but the field value1 is always null. As I could see, it seems like Gson looks for the fields in the interface (none) and not in the class implementing the interface.

Does anyone know how to solve this?

CodePudding user response:

InstanceCreator can only create instances of a class that does not define a no-args constructor. It does not handle deserialization. Gson still won't be able to figure out the concrete data type of the object.

Solution. Custom deserializer for interface
You need to define a custom deserializer for your interface and register this new type adapter in factory.

public class MyInterfaceDeserializer implements JsonDeserializer<MyInterface> {
    @Override
    public MyInterface deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        return jsonDeserializationContext.deserialize(jsonElement, MyInterfaceImpl.class);
    }
}

public class GsonFactory {
    public Gson create() {
        return new GsonBuilder()
                .setPrettyPrinting()
                .registerTypeAdapter(MyInterface.class, new MyInterfaceDeserializer())
                .create();
    }
}

OR Generic deserializer

public class InterfaceDeserializer<T, E> implements JsonDeserializer<T> {
    private final Class<E> implementationClass;

    public InterfaceDeserializer(Class<E> implementationClass) {
        this.implementationClass = implementationClass;
    }

    @Override
    public T deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        return jsonDeserializationContext.deserialize(jsonElement, implementationClass);
    }
}

public class GsonFactory {
    public Gson create() {     
        return new GsonBuilder()
                .setPrettyPrinting()                
               .registerTypeAdapter(MyInterface.class, new InterfaceDeserializer<MyInterface, MyInterfaceImpl>(MyInterfaceImpl.class))
                .create();
}

OR

public class MyInterfaceDeserializer implements JsonDeserializer<MyInterface> {
    @Override
    public MyInterface deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        MyInterface myInterface = new MyInterfaceImpl();
        JsonObject jObject = jsonElement.getAsJsonObject();
        myInterface.setValue1(jObject.get("value1").getAsLong());
        return myInterface;
    }
}

public class GsonFactory {
    public Gson create() {
        return new GsonBuilder()
                .setPrettyPrinting()
                .registerTypeAdapter(MyInterface.class, new MyInterfaceDeserializer())
                .create();
    }
}

Another option
Use RuntimeTypeAdapterFactory but this solution requires an extra type property in json to define the exact subtype, see the documentation.
Example:

    public Gson create() {
        RuntimeTypeAdapterFactory<MyInterface> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory.of(MyInterface.class).registerSubtype(MyInterfaceImpl.class, "MyInterfaceImpl");
        return new GsonBuilder()
                .setPrettyPrinting()
                .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
                .create();
    }

JSON must contain type field:

{
  myList: [
    {
      value1: 8,
      type: MyInterfaceImpl
    }
  ]
}

Please note gson-extras library required for that option. This artifact is located at CronApp repository.

  • Related