Course – LS (cat=JSON/Jackson)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll learn about the @Expose and @SerializedName annotations of the Gson library. @Expose helps control what class attributes can be serialized or deserialized, whereas @SerializedName helps map the object’s attribute names to the attribute key names in JSON string and vice versa while serializing and deserializing.

2. @Expose

There are use cases where certain sensitive values of attributes in a class shouldn’t be serialized or converted to JSON strings. To deal with this, Gson has the @Expose annotation, which has two Boolean attributes: serialize and deserialize.

Suppose the attribute password in the Person class should not serialize because it’s sensitive information. Hence, we must decorate the password attribute with the annotation @Expose(serialize=false):

public class Person {
    @Expose(serialize = true)
    private String firstName;
    @Expose(serialize = true)
    private String lastName;
    @Expose()
    private String emailAddress;
    @Expose(serialize = false)
    private String password;

    @Expose(serialize = true)
    private List<BankAccount> bankAccounts;
   //General getters and setters..
}

Similarly, accountNumber in the BankAccount object mustn’t serialize because it’s also sensitive information. Hence, we must decorate the accountNumber attribute also with the annotation @Expose(serialize=false):

public class BankAccount {
    @Expose(serialize = false, deserialize = false)
    private String accountNumber;
    @Expose(serialize = true, deserialize = true)
    private String bankName;
    //general getters and setters..
}

Now, to convert the object to a JSON string, we cannot use the default Gson object, which we create by using the new operator. We have to instantiate the Gson class with the configuration excludeFieldsWithoutExposeAnnotation() setting by using the GsonBuilder class.

Let’s look at the PersonSerializer class:

public class PersonSerializer {
    private static final Gson configuredGson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
    private static final Gson defaultGson = new Gson();

    public static String serializeWithConfiguredGson(Person person) {
       return configuredGson.toJson(person);
    }

    public static String serializeWithDefaultGson(Person person) {
        return defaultGson.toJson(person);
    }
}

Let’s test the method serializeWithConfiguredGson():

public class PersonSerializerUnitTest {
    @Test
    public void whenUseCustomGson_thenDonotSerializeAccountNumAndPassword () {
        String personJson = PersonSerializer.serializeWithConfiguredGson(person);
        assertFalse("Test failed: password found", personJson.contains("password"));
        assertFalse("Test failed: account number found", personJson.contains("accountNumber:"));
    }
}

As expected, we don’t see the sensitive attributes like password and accountNumber in the output:

{
  "firstName":"Parthiv",
  "lastName":"Pradhan","email":"[email protected]",
  "bankAccounts":[{"bankName":"Bank of America"},{"bankName":"Bank of America"}]
}

Similarly, let’s test the method serializeWithDefaultGson():

@Test
public void whenUseDefaultGson_thenSerializeAccountNumAndPassword () {
    String personJson = PersonSerializer.serializeWithDefaultGson(person);

    assertTrue("Test failed: password not found", personJson.contains("password"));
    assertTrue("Test failed: account number not found", personJson.contains("accountNumber"));
}

As discussed earlier, the defaultGson object fails to recognize the @Expose annotation and, as expected, the output prints password and accountNumber:

{
  "firstName":"James","lastName":"Cameron","email":"[email protected]",
  "password":"secret",
  "bankAccounts":
    [
      {"accountNumber":"4565432312","bankName":"Bank of America"},
      {"accountNumber":"4565432616","bankName":"Bank of America"}
    ]
}

To exclude attributes from serialization for more advanced use cases, we can use ExclusionStrategy.

3. @SerializedName

The @SerializedName annotation acts kind of like a custom transformer. Normally, we first convert the object into JSON string and then modify its attribute keys before sending it as a parameter to a web service.

Similarly, while converting the JSON string to an object, we must map its attribute keys to the object’s attribute name. The Gson library smartly combines both these steps with the help of one single annotation, @SerializedName. How simple is that!

Quite often, when we serialize, we want to generate a payload that’s as small as possible. Let’s try to serialize the below Country class with some custom key names that are much shorter than the attribute names:

public class Country {
    @SerializedName(value = "name")
    private String countryName;
    @SerializedName(value = "capital")
    private String countryCapital;
    @SerializedName(value = "continent")
    private String continentName;
    //general getters and setters..
}

Now, let’s convert the Country object into JSON:

public class PersonSerializer {
    private static final Gson defaultGson = new Gson();

    public static String toJsonString(Object obj) {
        return defaultGson.toJson(obj);
    }
}

It’s time to check if the method works:

@Test
public void whenUseSerializedAnnotation_thenUseSerializedNameinJsonString() {
    String countryJson = PersonSerializer.toJsonString(country);
    logger.info(countryJson);
    assertFalse("Test failed: No change in the keys", countryJson.contains("countryName"));
    assertFalse("Test failed: No change in the keys", countryJson.contains("contentName"));
    assertFalse("Test failed: No change in the keys", countryJson.contains("countryCapital"));

    assertTrue("Test failed: No change in the keys", countryJson.contains("name"));
    assertTrue("Test failed: No change in the keys", countryJson.contains("continent"));
    assertTrue("Test failed: No change in the keys", countryJson.contains("capital"));
}

As expected, we find that the attribute keys match what we provided to the @SerializedName annotation:

{"name":"India","capital":"New Delhi","continent":"Asia"}

Let’s see if the same annotation helps convert the above JSON into the Country object. To do this, we’d use the fromJsonString() method:

public class PersonSerializer {
    private static final Gson defaultGson = new Gson();
    public static Country fromJsonString(String json) {
        return defaultGson.fromJson(json, Country.class);
    }
}

Let’s check if the method works:

@Test
public void whenJsonStrCreatedWithCustomKeys_thenCreateObjUsingGson() {
    String countryJson = PersonSerializer.toJsonString(country);
    Country country = PersonSerializer.fromJsonString(countryJson);

    assertEquals("Fail: Object creation failed", country.getCountryName(), "India");
    assertEquals("Fail: Object creation failed", country.getCountryCapital(), "New Delhi");
    assertEquals("Fail: Object creation failed", country.getContinentName(), "Asia");
}

The method can create the Country object:

Country{countryName='India', countryCapital='New Delhi', continentName='Asia'}

4. Conclusion

In this article, we learned about two important Gson annotations: @Expose and @SerializedName. We can confidently say that both are completely different functionalities, as demonstrated here. The code snippets used in the article are available over on GitHub.

Course – LS (cat=JSON/Jackson)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.