Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to deserialize with RuntimeTypeAdapterFactory, " does not define a field named type"

Tags:

java

json

gson

I was trying to learn the concept of inheritance and deserialization of java beans through Gson framework. Details regarding java bean classes and json files are given below.

ParentBean.java

public class ParentBean {

    protected String key1;
    protected String key2;

    public ParentBean(String key1, String key2) {
        super();
        this.key1 = key1;
        this.key2 = key2;
    }

}

Bean1.java

public class Bean1 extends ParentBean {

    private String key3;

    public Bean1(String key1, String key2, String key3) {
        super(key1, key2);
        this.key3 = key3;
    }

}

Bean2.java

public class Bean2 extends ParentBean {

    private String key4;

    public Bean2(String key1, String key2, String key4) {
        super(key1, key2);
        this.key4 = key4;
    }

}

bean1.json

{
    "key1":"value1",
    "key2":"value2",
    "key3":"value33"
}

bean2.json

{ 
    "key1":"value1", 
    "key2":"value2", 
    "key4":"value43"
}

To explore things about inheritance and deserialization, I have used the following code:

Usage.java

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.Type;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;

public class Usage {

    public static void main(String[] args) throws FileNotFoundException  {

        RuntimeTypeAdapterFactory<ParentBean> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
                .of(ParentBean.class, "type")
                .registerSubtype(Bean1.class, "bean1")
                .registerSubtype(Bean2.class, "bean2");
        Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create();
        FileReader fr = new FileReader("bean1.json");
        Type pType = new TypeToken<ParentBean>(){}.getType();
        ParentBean pb = gson.fromJson(fr, pType);

         if (pb instanceof Bean1) {
                System.out.println(" Bean1");
            } else if (pb instanceof Bean2) {
                System.out.println("Bean2");
            } 
    }

}

I am getting an error stack which is as follows:

Exception in thread "main" com.google.gson.JsonParseException: cannot deserialize class inheritance.ParentBean because it does not define a field named type
    at com.google.gson.typeadapters.RuntimeTypeAdapterFactory$1.read(RuntimeTypeAdapterFactory.java:205)
    at com.google.gson.TypeAdapter$1.read(TypeAdapter.java:199)
    at com.google.gson.Gson.fromJson(Gson.java:795)
    at com.google.gson.Gson.fromJson(Gson.java:761)
    at inheritance.Usage.main(Usage.java:23)

In search of finding solution, I came across this stack overflow discussion. Unfortunately the discussion was about create() method. Error stack says the problem was with line 23 and this line contains fromJson() method.

like image 310
Nithin Avatar asked Feb 18 '18 18:02

Nithin


2 Answers

You need to tell gson more about the types. When serializing also the type needs to be serialized. So as the first comment by Jacob G. suggests you need the type field:

Docs for RuntimeTypeAdapterFactory.of(Class<T> baseType, String typeFieldName) states:

Creates a new runtime type adapter using for baseType using typeFieldName as the type field name. Type field names are case sensitive.

Add it to your ParentBean:

// Init it for serializing 
// You used values like 'bean1' & 'bean2' but using class name is more generic
protected String type = getClass().getName(); 

According to above changes in bean type names change the building of RuntimeTypeAdapterFactory accordingly:

RuntimeTypeAdapterFactory<ParentBean> runtimeTypeAdapterFactory = 
    RuntimeTypeAdapterFactory
        .of(ParentBean.class, "type") // typeFieldName
        .registerSubtype(Bean1.class, Bean1.class.getName())
        .registerSubtype(Bean2.class, Bean2.class.getName());

Finally - when de-serailizing - the Json files need also the type information which will be serialized from the field type so add it also fro both beans Json with correct package name:

"type":"org.example.gson.runtime.Bean1",

and

"type":"org.example.gson.runtime.Bean2",
like image 177
pirho Avatar answered Sep 28 '22 01:09

pirho


You do not need to explicitly add a type field to your beans. As pirho suggests, you need to have the type field in your json string. If you're crafting this by hand, just add the type field, e.g.

{
    "type":"bean1",
    "key1":"value1",
    "key2":"value2",
    "key3":"value33"
}

Presumably, you're also serializing your objects and to achieve that, you'll need to specify the base class during serialization.

As swankjesse points out in

https://github.com/google/gson/issues/712

Try replacing this:

    final String jsonStr = mGson.toJson(new Child());

With this:

    final String jsonStr = mGson.toJson(new Child(), Base.class);

Then, when you serialize your types, your output json will be

{
    "type":"bean1",
    "key1":"value1",
    "key2":"value2",
    "key3":"value33"
}

That way, your beans can stay pure, without knowing that they are providing some type key to be used in serialization.

like image 31
Michael Bartholomew Avatar answered Sep 27 '22 23:09

Michael Bartholomew