6

My situation is as follows: Object TableC has 4 fields. Only 3 fields (field_C1, field_C2, and field_C3) are read from the JSON string. The fourth field field_C4 is defined within the object with a default value.

When I serialize the object instance (for output) - it ignores the field field_C4, I was expecting the default value of "1" or "null". When I explicitly define a value to the instance-field within the program to "NEW", it does include it in the Json output string.

Looking at the output, it looks as if the constructor is also ignored when the object instance is created during deserialization.

What would be the best practice to activate the other fields for the object instance - which are not included in the Deserialized version of the input Json String?

package newpackage;
import java.util.List;

import com.google.gson.*;


public class jsonwithconstructor {

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

    String jsonstring = "{'TableC':["
            + "{'field_C1':'C_11','field_C2':'C_12','field_C3':'C_13'},"
            + "{'field_C1':'C_21','field_C2':'C_22','field_C3':'C_23'}"
            + "]}";

    jsonstring = jsonstring.replace('\'', '"');
    System.out.println(jsonstring); 

    RootObject root = new GsonBuilder().create().fromJson(jsonstring, RootObject.class);

    for (int i=0; i < root.TableC.size(); i++){
        System.out.println(root.TableC.get(i));
    }

    System.out.println();

    //root.TableC.get(0).field_C4 = "NEW";

    for (int i=0; i < root.TableC.size(); i++){
        System.out.println(root.TableC.get(i));
    }
    System.out.println();

    Gson gson = new Gson();

    String jsonoutput = gson.toJson(root);
    System.out.println(jsonoutput); 


}

public class TableC{
    public String field_C1;
    public String field_C2;
    public String field_C3;
    public String field_C4 = "1";

    public TableC(){
        this.field_C4 = "1";
    }

    @Override
    public String toString() {
        return ("TableC" + ", " + this.field_C1 + ", " + this.field_C2 + ", " + this.field_C3 + ", " + this.field_C4);
    }
}
public class RootObject{

    public List<TableC> TableC; 

}

}

The output is shown below:

{"TableC":[{"field_C1":"C_11","field_C2":"C_12","field_C3":"C_13"},{"field_C1":"C_21","field_C2":"C_22","field_C3":"C_23"}]}
TableC, C_11, C_12, C_13, null
TableC, C_21, C_22, C_23, null

TableC, C_11, C_12, C_13, NEW
TableC, C_21, C_22, C_23, null

{"TableC":[{"field_C1":"C_11","field_C2":"C_12","field_C3":"C_13","field_C4":"NEW"},{"field_C1":"C_21","field_C2":"C_22","field_C3":"C_23"}]}
durron597
  • 30,764
  • 16
  • 92
  • 150
userDSSR
  • 617
  • 1
  • 8
  • 15

3 Answers3

2

This is a known, currently open issue: https://github.com/google/gson/issues/513

Gson constructs the values of fields in deserialized objects with reflection, so it will set the values based on what's in the JSON only. Until Google provides a fix for this issue, there isn't that much you can do.

There are a number of workaround objects you have in the meantime:

  1. Wrap the fields in getters, and lazily load the value. This is a good way (and my personal recommendation) to do it if a field is never ever allowed to be null, but they do need to be mutable.
  2. Mark the default fields as final. This a good way to do it if they are immutable.
  3. Create a custom ExclusionStrategy, and mark the particular fields that should be ignored using FieldAttributes
    • This is the most versatile of the options but also the most code.
  4. Deserialize your POJO using just the fields that don't exist, and then compose that data structure with a new one that has the default values.

I agree that all of these have drawbacks, but as I said above, this is an open issue with Gson.

Community
  • 1
  • 1
durron597
  • 30,764
  • 16
  • 92
  • 150
  • Thanks @durron597 - for the edits and the answer. I was also inclining towards a workaround - but wanted to check with the community first. Thanks. – userDSSR Sep 10 '15 at 21:40
  • @userDSSR Glad I could help. Out of curiosity, which option are you leaning towards? – durron597 Sep 10 '15 at 21:44
  • I was going to initialize the "fields" that do not appear in the Json - through a method within the object. After the serialization - I could use a "for" loop to run through all the objects and initialize the rest of the fields. Some of these fields have default values, but others also have derived values. – userDSSR Sep 10 '15 at 21:52
  • @userDSSR Okay. Personally I recommend option 1 if the fields are never allowed to be null, you don't have to have a separate step of running an initialize method on all the objects that way. See [Lazy Loading](http://stackoverflow.com/q/36274/1768232) – durron597 Sep 10 '15 at 21:57
  • Thanks @durron597. I just checked - "Lazy Loading" and it makes perfect sense. Yes, it does save me writing an additional initialize method. Thanks for the recommendation. – userDSSR Sep 10 '15 at 22:12
  • I can't believe this still hasn't been fixed. – RED_ Jun 01 '17 at 13:43
1

Using Gson v2.7, I can see that it calls the default constructor of the deserialized object and any value initialized inside that is retained. However, I was using "new Gson()" instead of "GsonBuilder", if that makes any difference.

ravindu1024
  • 1,330
  • 11
  • 27
  • I can confirm this using Gson 2.8.2: If you provide a default no-args constructor, it is called to initiate the object. Providing a no-args constructor is apparently recommended anyway: https://stackoverflow.com/questions/18645050/is-default-no-args-constructor-mandatory-for-gson – guglhupf Dec 20 '17 at 00:33
0

I'm currently using this approach on v2.8.5:

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

And parse it like:

Test test = new GsonBuilder().create().fromJson(jsonString, Test.class);
Sdghasemi
  • 3,927
  • 1
  • 24
  • 32