Search code examples
javajsonrequestgsonjson-deserialization

Questions about JSON/Gson serialization and deserialization


I'm pretty new to serialization in Java, and I have several questions I've gathered as I've been reading into it.

say you want to deserialize a json response, and that json response has a bunch of fields (eg - a, b,c,d,e,f,g,h) but you only want a,b,c. the api youre trying to hit is paginated, so you loop through them. also you declare a metadataclass, say Sample.java like so to only include the fields you want:

@Data 
@NoArgsConstructor
@AllArgsConstructor

public class Sample {
    private String a;
    private String b;
    private String c;
}

say your service class does this:

UriComponentsBuilder createBranchUri = UriComponentsBuilder.fromUriString(requestUrl);
try { 
    Gson gson = new Gson(); 
    Type sampleListType = new TypeToken<List<Sample>>() {}.getType(); 
    //loop through the pages and make requests JsonArray sampleLines =      paginatedRequest(restTemplate, createBranchUri, "values"); 
    List<Sample> stashLines = gson.fromJson(sampleLines, sampleListType); 
..........

is this allowed? meaning will it handle the serialization without erroring and properly assign the values for String a, b and c? or are we only able to "exclude" fields by using exclusion strategies like the ones described here or by directly indexing into the json object. worded another way - what is the default behavior when gson is parsing a response (in this case: d,e...), and it can't find an equivalent variable in the Type? In addition, does order matter? say the order of mappings is a, b, c, d.... what happens if we switch where a and b are in Sample?


Solution

  • @Data 
    @NoArgsConstructor
    @AllArgsConstructor
    public class Sample {
        ...
    }
    

    To avoid any misunderstandings: Gson only uses the no-args constructor and sets and gets field values using reflection, without using any setter or getter methods. (The only exception are Java Record Classes where Gson does use the args constructor and accessor methods.)

    what is the default behavior when gson is parsing a response (in this case: d,e...), and it can't find an equivalent variable in the Type?

    It just ignores them. But this is unfortunately also something you cannot configure at the moment, see Gson issue 188.

    "exclude" fields by using exclusion strategies

    Gson's ExclusionStrategy is rather for the opposite use case: A field exists in your Java class but you don't want it to be serialized or deserialized. For simple cases you can also make the field transient to exclude it.

    In addition, does order matter? say the order of mappings is a, b, c, d.... what happens if we switch where a and b are in Sample?

    The order of fields in the Java class does not matter when deserializing from JSON, but it does matter when serializing to JSON.
    The order in the JSON data when deserializing does not matter in most cases, and in your use case most likely it doesn't. Cases where it does matter are:

    • A JSON object contains members with duplicate names. Then normally the last occurrence will be used, for example {"a":1,"a":2} will have the same effect as {"a":2}.
    • You are deserializing as Map, Object or JsonObject. Then Gson will create a Map instance which preserves the order, which can be seen when you iterate for example over Map.entrySet(). But the different iteration order might not (and probably should not) matter for your application logic.

    Type sampleListType = new TypeToken<List<Sample>>() {}.getType(); 
    List<Sample> stashLines = gson.fromJson(sampleLines, sampleListType);
    

    In Gson version >= 2.10 you can use the Gson.fromJson(..., TypeToken) overloads. You should prefer them over the Gson.fromJson(..., Type) overloads you are using because they are type-safe[1]. So you could change your code to:

    TypeToken<List<Sample>> sampleListType = new TypeToken<List<Sample>>() {}; 
    List<Sample> stashLines = gson.fromJson(sampleLines, sampleListType);
    

    In Java 9 this becomes more concise because you can use the 'diamond form' with anonymous classes:
    TypeToken<List<Sample>> sampleListType = new TypeToken<>() {};
    And in Java 10 you can alternatively use var:
    var sampleListType = new TypeToken<List<Sample>>() {};


    [1]: For example Gson.fromJson(..., Type) lets you write non-type-safe code like this, which compiles without warnings but fails at runtime:

    // Note: Mismatch between `WrongClass` and `MyClass`
    List<WrongClass> list = new Gson().fromJson("[{}]", new TypeToken<List<MyClass>>() {}.getType());
    // Throws ClassCastException
    WrongClass first = list.get(0);