1. 概述

在这个教程中,我们将了解Gson库中的@Expose@SerializedName注解。@Expose帮助控制类属性在序列化或反序列化过程中的处理,而@SerializedName则用于映射对象属性名称与JSON字符串中的键名,反之亦然。

2. @Expose

有些情况下,类属性的某些敏感值不应被序列化或转换为JSON字符串。为此,Gson提供了@Expose注解,它有两个布尔属性:serializedeserialize

假设Person类中的password属性因为敏感信息不应被序列化,我们应使用@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..
}

类似地,BankAccount对象中的accountNumber属性也因为敏感信息不应被序列化,同样需要使用@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..
}

现在,当我们想要将对象转换为JSON字符串时,不能直接使用默认的Gson对象(通过new关键字创建),而是需要使用GsonBuilder类,通过配置excludeFieldsWithoutExposeAnnotation()来实例化具有此设置的Gson

让我们看看PersonSerializer类:

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

测试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:"));
    }
}

如预期,输出中并未包含敏感属性passwordaccountNumber

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

同样,测试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"));
}

正如之前所述,defaultGson对象无法识别@Expose注解,结果输出了passwordaccountNumber

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

对于更复杂的场景,要排除属性的序列化,可以使用ExclusionStrategy(详情参阅/gson-exclude-fields-serialization#exclusionstrategy)。

3. @SerializedName

@SerializedName注解相当于一个自定义转换器。通常,我们会先将对象转换为JSON字符串,然后修改其属性键,再作为参数发送到Web服务。

同样,在将JSON字符串转换为对象时,我们需要将其属性键映射到对象的属性名称。Gson库利用@SerializedName这个单一注解巧妙地结合了这两个步骤,多么简洁!

很多时候,我们在序列化时希望生成尽可能小的payload。让我们尝试使用一些比属性名称短得多的自定义键名序列化Country类:

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

现在,将Country对象转换为JSON:

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

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

检查方法是否有效:

@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"));
}

正如预期,我们发现属性键与注解中提供的名称匹配:

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

让我们看看这个注解是否能帮助将上述JSON转换为Country对象。为此,我们可以使用fromJsonString()方法:

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

检查方法是否工作:

@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");
}

方法能够创建Country对象:

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

4. 总结

在这篇文章中,我们学习了Gson库中的两个重要注解:@Expose@SerializedName。它们的功能完全不同,如文中所示。文章中使用的代码示例可在GitHub上找到。