Skip to content

Registering a deserializer changes behavior of serialization #2032

@JohnnyJayJay

Description

@JohnnyJayJay

Gson version

2.8.9

Java / Android version

Java SE 17 (OpenJDK)

Used tools

  • Maven; version:
  • Gradle; version: 7.3.1
  • ProGuard (attach the configuration file please); version:
  • ...

Description

Serialisation behaviour changes based on whether a custom deserialiser is set or not. More specifically, my objects are not serialised in their entirety if a custom deserialiser is provided.

Here is a case to illustrate:

public static abstract class A {
    private String x = "hello";
}
public static class B extends A {
    private int y = 12;
}
public static class C extends A {
    private double z = 231.2;
}
public static class Foo {
    private A a;
}

Creating a Foo object, assigning its a field to new B() or new C() results in the intuitive behaviour when serialising the Foo object:

{
  "a": {
    "y": 12,
    "x": "hello"
  }
}

However, when a custom deserialiser for A is set using registerTypeAdapter, this changes to

{
  "a": {
    "x": "hello"
  }
}

Expected behavior

I expect the behaviour to stay the same, whether a custom deserialiser is registered or not.

Actual behavior

The behaviour changes and fields of sub classes are omitted in the result.

Reproduction steps

public class Demo {
    static void caseOne() {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        Foo src = new Foo();
        src.a = new B();
        String out = gson.toJson(src);
        System.out.println(out);
    }
    static void caseTwo() {
        JsonDeserializer<A> deserializer = (elem, t, c) -> new B();
        Gson gson = new GsonBuilder().setPrettyPrinting()
                .registerTypeAdapter(A.class, deserializer)
                .create();
        Foo src = new Foo();
        src.a = new B();
        String out = gson.toJson(src);
        System.out.println(out);
    }
    public static abstract class A {
        private String x = "hello";
    }
    public static class B extends A {
        private int y = 12;
    }
    public static class C extends A {
        private double z = 231.2;
    }
    public static class Foo {
        private A a;
    }
}

The above class can be used to reproduce this behaviour.

Exception stack trace

/

Additional notes

This issue seems similar to #1599 , however I am not satisfied with the answer provided there. If the intent behind this behaviour was to be able to guarantee that any serialised object can be deserialised again, why does caseOne work although the result can obviously not be deserialised using Gson defaults anymore?

I am also aware that using registerTypeHierarchyAdapter instead "fixes" the problem. However I don't consider this a fix because it changes the entire semantics of the custom deserialiser. For instance, I can't use the deserialisation context anymore to delegate deserialisation. E.g. context.deserialize(something, B.class) doesn't work anymore because it's recursive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions