Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] sub class field missing during serialization if collection's element type is a WildcardType #1870

Open
cnzhujie opened this issue Mar 3, 2021 · 2 comments
Labels

Comments

@cnzhujie
Copy link

cnzhujie commented Mar 3, 2021

I am serializing a list like this:

List<? extends TestBeanBase> list1;
list1.add(new TestBeanSub());    // TestBeanSub is a child class of TestBeanBase

I find that the fields of TestBeanSub is missed in the serialized json.

You can try my example code:

    @Test
    public void testList() {
        List<TestBeanBase> list = new ArrayList<>();

        //base class
        TestBeanBase baseBean = new TestBeanBase();
        baseBean.a = "1";
        list.add(baseBean);

        //sub class
        TestBeanSub subBean = new TestBeanSub();
        subBean.a = "1";
        subBean.b = "2";//b is a field declared in sub class, this field value is missed during serialization
        list.add(subBean);

        TestBeanList beanList = new TestBeanList();
        beanList.list1 = list;

        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        System.out.println(gson.toJson(beanList));
    }

    public static class TestBeanList {
        public List<? extends TestBeanBase> list1; //element type is a WildcardType
    }

    public static class TestBeanBase {
        public String a;
    }

    public static class TestBeanSub extends TestBeanBase {
        public String b;
    }

Output json :

{
  "list1": [
    {
      "a": "1"
    },
    {
      "a": "1"   //field b is missed
    }
  ]
}
@Marcono1234
Copy link
Collaborator

There is #170 which seems to exactly describe your problem, but it was merged into #231 despite that issue apparently covering a completely different use case.

The underlying issue is that TypeAdapterRuntimeTypeWrapper.getRuntimeTypeIfMoreSpecific(Type, Object) (internal Gson method) does not choose the runtime type when a wildcard is used (which might be a bug?).

A workaround for this would be to create a custom TypeAdapterFactory which always uses the adapter for the runtime type:

class RuntimeTypeAdapterFactory implements TypeAdapterFactory {
    public static final RuntimeTypeAdapterFactory INSTANCE = new RuntimeTypeAdapterFactory();
    
    private RuntimeTypeAdapterFactory() { }
    
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        
        return new TypeAdapter<T>() {
            @Override
            public T read(JsonReader in) throws IOException {
                return delegate.read(in);
            }
            
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                if (value == null) {
                    // Let compile-time type adapter handle `null`
                    delegate.write(out, null);
                }
                else {
                    @SuppressWarnings("unchecked")
                    TypeToken<T> runtimeType = (TypeToken<T>) TypeToken.get(value.getClass());
                    // Get adapter for runtime type
                    TypeAdapter<T> delegate = gson.getDelegateAdapter(RuntimeTypeAdapterFactory.this, runtimeType);
                    delegate.write(out, value);
                }
            }
        };
    }
}

This factory would then have to be registered after all other factories and adapters on the GsonBuilder:

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.INSTANCE)
    .create();

This will decrease performance and might have unintended side effects when you actually want to use the adapter for the compile-time type in some cases.

@cnzhujie
Copy link
Author

cnzhujie commented Mar 9, 2021

There is #170 which seems to exactly describe your problem, but it was merged into #231 despite that issue apparently covering a completely different use case.

The underlying issue is that TypeAdapterRuntimeTypeWrapper.getRuntimeTypeIfMoreSpecific(Type, Object) (internal Gson method) does not choose the runtime type when a wildcard is used (which might be a bug?).

A workaround for this would be to create a custom TypeAdapterFactory which always uses the adapter for the runtime type:

class RuntimeTypeAdapterFactory implements TypeAdapterFactory {
    public static final RuntimeTypeAdapterFactory INSTANCE = new RuntimeTypeAdapterFactory();
    
    private RuntimeTypeAdapterFactory() { }
    
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        
        return new TypeAdapter<T>() {
            @Override
            public T read(JsonReader in) throws IOException {
                return delegate.read(in);
            }
            
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                if (value == null) {
                    // Let compile-time type adapter handle `null`
                    delegate.write(out, null);
                }
                else {
                    @SuppressWarnings("unchecked")
                    TypeToken<T> runtimeType = (TypeToken<T>) TypeToken.get(value.getClass());
                    // Get adapter for runtime type
                    TypeAdapter<T> delegate = gson.getDelegateAdapter(RuntimeTypeAdapterFactory.this, runtimeType);
                    delegate.write(out, value);
                }
            }
        };
    }
}

This factory would then have to be registered after all other factories and adapters on the GsonBuilder:

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.INSTANCE)
    .create();

This will decrease performance and might have unintended side effects when you actually want to use the adapter for the compile-time type in some cases.

Thanks, it works for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants