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

Created on 19 Mar 2015  路  31Comments  路  Source: google/gson

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 [email protected] on 5 May 2013 at 8:23

Most helpful comment

Any update on this?

All 31 comments

Is this still an issue.  Did u try it in 2.2.4?

Its working for me on Windows with GSON 2.2.4

Original comment by [email protected] on 3 Aug 2013 at 1:22

Hari,

Yes it is. I've just tested with Gson 2.2.4 on Fedora 19, 64 bit with OpenJDK 
64-Bit Server VM (build 23.7-b01, mixed mode).

Original comment by [email protected] on 4 Aug 2013 at 11:35

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 [email protected] on 22 Nov 2013 at 1:12

@krt...

Thanks for your help. Unfortunately it's not working. I still got "x=[0] 
s=[null]". Gson v2.2.4.

Original comment by [email protected] on 22 Nov 2013 at 7:28

Confirm, this is still not working.
No way to assign default values for fields.
If field is missing in JSON it is deserialized as null.
Gson v2.2.4

Original comment by [email protected] on 31 Jan 2014 at 7:16

This is most probably related to issue 495, see the links in the posting from 
Jun 23, 2013.

Original comment by [email protected] on 2 Feb 2014 at 12:32

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

Ya man...i'm also stuck with this

Is it solved? 2016 Nov, missing field should have method to get ignored.

@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

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

Have you posted in StackOverflow? If not kindly post it there and share the link here. Let's discuss it there.

How has this not been fixed it? Is it still being looked at? It's a big problem.

Fields that have nothing to do with the JSON return should not be modified at all.

Any update on this?

I tried in gson 2.8.1,it is already fixed.
public class User { String name = "username"; int x = -1; }
public static void main(String[] args) { User u = new Gson().fromJson("{}", User.class); System.out.println(String.format("x=[%s] s=[%s]", u.name,u.x)); }
result:x=[username] s=[-1]

Not working for me with 2.8.1.

  public class User {
    String name = "username";
    int x = -1;
  }

  public static void main(String[] args) {
    User u = new Gson().fromJson("{}", User.class);
    System.out.println(String.format("x=[%s] s=[%s]", u.name,u.x));
  }

result:

x=[null] s=[0]

This seems to correspond to the documentation:

While deserializing, a missing entry in JSON results in setting the corresponding field in the object to null

I am sorry for my poor Engilsh.
I found somecode in gson.
` TypeAdapter typeAdapter = getAdapter(typeToken);

T object = typeAdapter.read(reader); And I go into the class ReflectiveTypeAdapterFactory and the method,I found 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);
}
}`

First,the code user the java reflect Constructor.newInstance(Object... args),
although args ==null ,the instance initialize its parameter.
In my case,the name is initialize to username.
And in.hasNext() == false , return instance.
The result is username not null.

Do you mean you find some code and used it in you own application? Perhaps you have registered a type adapter somewhere before. If so, can you post a working example? (To preserve formatting, insert you code examples between two lines containing only three backticks (`), such as:


public class User {
    String name = "username";
    int x = -1;
}

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.

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

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.

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)

However I'm disappointed, I'm currently using this approach:

public class Test {
    @SerializedName("f")
    private int foo;
    @SerializedName("b")
    private int bar;

    public Test() {
        bar = -1;    // Uses -1 as default value for bar variable when it's not present in the JSON
    }
    ...
}

BTW I'm currently using v2.8.5.

I changed the logic of my code to have a defaults JSON file that then gets mapped over and adds values where missing.

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;
}

@ahoyland

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

did you find solution in other lib?
if you did please mention it.馃檹馃徎

any actual updates for this?

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;
}

the ReflectiveTypeAdapterFactory is final, we cannot inherit it. any other approach? @lll-01

  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.

Still a problem.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JakeWharton picture JakeWharton  路  39Comments

adiantek picture adiantek  路  23Comments

GoogleCodeExporter picture GoogleCodeExporter  路  32Comments

GoogleCodeExporter picture GoogleCodeExporter  路  25Comments

RobMans426 picture RobMans426  路  20Comments