gson: Deserialization with Gson.fromJson() should *ignore* missing fields

What steps will reproduce the problem?

1. Create a simple *non-static* class which initializes default values for some 
fields:

    public class Test {

        int x = -1;
        String s = "Hello";
    }

2. Create new Gson and call `fromJson()` with missing fields:

    Test t = new Gson().fromJson("{}", Test.class);
    System.out.println(String.format("x=[%s] s=[%s]", t.x, t.s));

>> Results: x=[0] s=[null]

    Test t = new Gson().fromJson("{\"x\": 1}", Test.class);
    System.out.println(String.format("x=[%s] s=[%s]", t.x, t.s));

>> Results: x=[1] s=[null]

What is the expected output? What do you see instead?

As the test cases, I would like to expect default value for each field to be 
the one that the class initializes. For instance with the first test case, `x` 
should be `-1` and `s` should be "Hello".

What version of the product are you using? On what operating system?

 - Gson 2.2.3
 - OSes:
   + Fedora 18, 64 bit with OpenJDK 1.7.0_19;
   + Android API 4 and API 17;

Thanks,
Hai

Original issue reported on code.google.com by haibison...@gmail.com on 5 May 2013 at 8:23

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 37
  • Comments: 33 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Still a problem.

Any update on this?

This is still a problem. I’m going to have to use a different library because of this. Very disappointing…

For me it works with 2.8.5 only if all fields have defaults

    private data class Foo(
        @SerializedName("claus") val santa: Boolean = false
        @SerializedName("norris") val chuck: Boolean = true
    )

   val foo = Gson().fromJson<Foo>("{}", Foo::class.java) // --> Foo(santa=false, chuck=true)

but when one has no default

    private data class Foo(
        @SerializedName("claus") val santa: Boolean
        @SerializedName("norris") val chuck: Boolean = true
    )

   val foo = Gson().fromJson<Foo>("{}", Foo::class.java) // --> Foo(santa=false, chuck=false)

Works in 2.8.2. Declare the User class static or define it outside of another class. Update: actually, it works in 2.2.4 too.

Apparently, if you have a default constructor for the class, it will work as 
you like.

public Test() { }

and that should be enough for the instance variables to be initialized as 
declared.

Original comment by krt...@gmail.com on 22 Nov 2013 at 1:12

This is still a problem, is anyone looking into this? How do we prevent null fields from being deserialized?

The 2020 comment from @Marcono1234 remains correct. If you are seeing this problem for a class that is not an inner class (it’s a top-level class, or it’s static) and that has a no-arg constructor, then please open a separate bug with a minimal test case that reproduces the problem. Otherwise, the solution is probably to register a TypeAdapter for your class.

Concerning Kotlin, it doesn’t work if some but not all fields in a data class have default values. If all fields do have default values then the Kotlin compiler generates a no-arg constructor:

On the JVM, if all of the primary constructor parameters have default values, the compiler will generate an additional parameterless constructor which will use the default values. This makes it easier to use Kotlin with libraries such as Jackson or JPA that create class instances through parameterless constructors.

  1. Create a simple non-static class which initializes default values for some fields:

This part of the reproduction steps hints where the issue is. Since the class is an non-static (i.e. inner) nested class the compiler adds a synthetic constructor parameter for passing the enclosing instance. Therefore Gson is not able to find a no-parameter constructor and falls back to using the JDK internal method sun.misc.Unsafe.allocateInstance whose documentation says:

Allocates an instance but does not run any constructor.

(which apparently includes the initializers assigning the field values as well)

There are at least two solutions for this:

  • make the inner class static; often this is possible without any additional code changes and likely will also improve the performance of your code
  • use an InstanceCreator; this can be more tricky since you somehow have to access an instance of the enclosing class to create an instance of the inner class. Implementing this correctly and in a sane way depends on your use case.

The Kotlin case described in https://github.com/google/gson/issues/513#issuecomment-447299253 seems to be similar. It appears when all fields have a default value the Kotlin compiler creates a no-parameter constructor. However, as soon as one field has no default value there won’t be a no-parameter constructor so Gson falls back to using Unsafe as described above. The solution here could be to add a default value for all fields (even if you expect some of them to always be overwritten) or to write a custom TypeAdapter.

modify ReflectiveTypeAdapterFactory

 @Override
 void read(JsonReader reader, Object value)
         throws IOException, IllegalAccessException {
     Object fieldValue = typeAdapter.read(reader);
     if (fieldValue == null && !context.deserializeNulls()) return;// Do not write null value
     if (fieldValue != null || !isPrimitive) {
         field.set(value, fieldValue);
     }
 }

add deserializeNulls to GsonBuilder and Gson

/**
 * Configure Gson to deserialize null fields. By default, Gson omits all fields that are null
 * during deserialization.
 *
 * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
 * @since 1.2
 */
public GsonBuilder deserializeNulls() {
    this.deserializeNulls = true;
    return this;
}

Thank you. I didn’t describe clearly. I debug the Gson source code.And I found why my result is username not null. You can follow the call chain,you will found the method in Gson.java

public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
    boolean isEmpty = true;
   boolean oldLenient = reader.isLenient();
   reader.setLenient(true);
   try {
     reader.peek();
     isEmpty = false;
     TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
     TypeAdapter<T> typeAdapter = getAdapter(typeToken);
     T object = typeAdapter.read(reader);
     return object;
   } 

and the typeAdapter is instanceof Adapter in ReflectiveTypeAdapterFactory. and the method read() is

T instance = constructor.construct();

      try {
        in.beginObject();
        while (in.hasNext()) {
          String name = in.nextName();
          BoundField field = boundFields.get(name);
          if (field == null || !field.deserialized) {
            in.skipValue();
          } else {
            field.read(in, instance);
          }
        }
      }
     return instance;
 

‘T instance = constructor.construct();’ After this,instance is initialize its parameter. In my code, the parameter ‘name’ is initialize to ‘username’. And in.hasNext()==false, so the method just return instance. For more detail, the code ’ constructor.construct()’ user the java reflect

Constructor.newInstance(Object... args)

although ‘args ==null’ ,the instance initialize its parameter with default value. I did in jdk1.7 and jdk1.8 . Did I describe clearly ? I tried my best. Sorry , I will improve my English.

I’m having the same problem right now. That link does not help, it’s about something else @VyshnavKR

@stayclean I don’t remember the solution but I have done it all well. Will this help? stackoverflow.com/questions/33435683/deserialize-with-gson-and-null-values