Android 基本 Jackson Marshalling(serialize)/Unmarshalling(deserialize)

时间:2023-03-10 03:56:51
Android 基本 Jackson Marshalling(serialize)/Unmarshalling(deserialize)

本文内容

  • 基本 Jack Marshalling
    • 忽略属性
    • 忽略 Null 字段
    • 改变字段名字
  • 基本 Jackson Marshalling
    • 把 JSON 解析成 JsonNode
    • Unmarshalling 带未知属性的 json
  • 演示
  • 参考资料
  • 术语

本文使用 Jackson 2,包括 jackson-annotations-2.4.0.jar、jackson-core-2.4.1.jar 和 jackson-databind-2.4.1.jar 这三个库。

貌似太理论的东西,看得人也比较少,都喜欢看实际功能的东西,不过啊,只关注功能、理论太弱的人,基本没前途~

下载 Demo

下载 Jackson 2

基本 Jackson Marshalling


如何把一个 Java 实体序列化(serialize)成一个 JSON 字符串,并且如何控制映射的过程,以便获得准确的你想要的 JSON 格式。

忽略属性

当 Jackson 默认值不够,我们就需要准确地控制把什么序列化成 JSON,此时就非常有用了。有很多方式来忽略属性。

  • 在类的级别上忽略字段(field)

通过使用 @JsonIgnoreProperties 注解(annotation)和指定字段名字,我们可以在类的级别上忽略指定的字段:

@JsonIgnoreProperties(value = { "intValue" })

public class MyDto {

 

    private String stringValue;

    private int intValue;

    private boolean booleanValue;

 

    public MyDto() {

        super();

    }

 

    // standard setters and getters are not shown

}

下面的测试会通过。对象被序列化成 JSON 后,里边的确没有 intValue 字段:

@Test

public void givenFieldIsIgnoredByName_whenDtoIsSerialized_thenCorrect()

  throws JsonParseException, IOException {

    ObjectMapper mapper = new ObjectMapper();

    MyDto dtoObject = new MyDto();

 

    String dtoAsString = mapper.writeValueAsString(dtoObject);

 

    assertThat(dtoAsString, not(containsString("intValue")));

}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

  • 在字段的级别上忽略字段

也可以通过在字段上使用 @JsonIgnore 注解,直接忽略字段:

public class MyDto {

 

    private String stringValue;

    @JsonIgnore

    private int intValue;

    private boolean booleanValue;

 

    public MyDto() {

        super();

    }

 

    // standard setters and getters are not shown

}

测试代码如下,序列化后的 JSON 不包含 intValue 字段:

@Test

public void givenFieldIsIgnoredDirectly_whenDtoIsSerialized_thenCorrect()

  throws JsonParseException, IOException {

    ObjectMapper mapper = new ObjectMapper();

    MyDto dtoObject = new MyDto();

 

    String dtoAsString = mapper.writeValueAsString(dtoObject);

 

    assertThat(dtoAsString, not(containsString("intValue")));

}

  • 根据类型忽略字段

通过使用 @JsonIgnoreType 注解,我们可以忽略所有指定类型的字段:

@JsonIgnoreType

public class SomeType { ... }

通常情况下,我们不能控制类本身;在这种情况下,我们可以好好利用 Jackson mixins。

首先,我们为类型定义一个 MixIn, 并用 @JsonIgnoreType 注解:

@JsonIgnoreType

public class MyMixInForString {

    //

}

然后,向 mixin 注册在 marshalling 期间忽略掉所有的 String 类型:

mapper.addMixInAnnotations(String.class, MyMixInForString.class);

这样,所有的 String 类型将被忽略,不会被序列化成 JSON:

@Test

public final void givenFieldTypeIsIgnored_whenDtoIsSerialized_thenCorrect()

  throws JsonParseException, IOException {

    ObjectMapper mapper = new ObjectMapper();

    mapper.addMixInAnnotations(String.class, MyMixInForString.class);

    MyDto dtoObject = new MyDto();

    dtoObject.setBooleanValue(true);

 

    String dtoAsString = mapper.writeValueAsString(dtoObject);

 

    assertThat(dtoAsString, containsString("intValue"));

    assertThat(dtoAsString, containsString("booleanValue"));

    assertThat(dtoAsString, not(containsString("stringValue")));

}

  • 使用 Filter 忽略字段

可以使用 Filter 忽略指定的字段。

首先,我们需要在 Java 对象上定义 filter:

@JsonFilter("myFilter")

public class MyDtoWithFilter { ... }

然后,定义一个简单的 filter,忽略 intValue 字段:

SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAllExcept("intValue");

FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);

现在序列化对象,确保 intValue 字段不会出现在 JSON 中:

@Test

public final void givenTypeHasFilterThatIgnoresFieldByName_whenDtoIsSerialized_thenCorrect()

  throws JsonParseException, IOException {

    ObjectMapper mapper = new ObjectMapper();

    SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAllExcept("intValue");

    FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);

 

    MyDtoWithFilter dtoObject = new MyDtoWithFilter();

    String dtoAsString = mapper.writer(filters).writeValueAsString(dtoObject);

 

    assertThat(dtoAsString, not(containsString("intValue")));

    assertThat(dtoAsString, containsString("booleanValue"));

    assertThat(dtoAsString, containsString("stringValue"));

    System.out.println(dtoAsString);

}

忽略 Null 字段

本小节说明,当序列化一个 Java 类时,如何忽略 null 字段。

  • 在类的级别上忽略 Null 字段

Jackson 允许在类的级别上控制这个行为:

@JsonInclude(Include.NON_NULL)

public class MyDto { ... }

或是,在更小粒度上,字段级别上:

public class MyDto {

 

    @JsonInclude(Include.NON_NULL)

    private String stringValue;

 

    private int intValue;

 

    // standard getters and setters

}

现在测试一下,null 值不会出现在 JSON 文件:

@Test

public void givenNullsIgnoredOnClass_whenWritingObjectWithNullField_thenIgnored()

  throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();

    MyDto dtoObject = new MyDto();

 

    String dtoAsString = mapper.writeValueAsString(dtoObject);

 

    assertThat(dtoAsString, containsString("intValue"));

    assertThat(dtoAsString, not(containsString("stringValue")));

}

  • 全局忽略 Null 字段

Jackson 也允许在 ObjectMapper 上全局配置这一行为:

mapper.setSerializationInclusion(Include.NON_NULL);

现在,在任何类中的 null 字段都不会被序列化:

@Test

public void givenNullsIgnoredGlobally_whenWritingObjectWithNullField_thenIgnored() 

  throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();

    mapper.setSerializationInclusion(Include.NON_NULL);

    MyDto dtoObject = new MyDto();

 

    String dtoAsString = mapper.writeValueAsString(dtoObject);

 

    assertThat(dtoAsString, containsString("intValue"));

    assertThat(dtoAsString, containsString("booleanValue"));

    assertThat(dtoAsString, not(containsString("stringValue")));

}

改变字段名字

如何在序列化时,改变字段名字,映射到另一个 JSON 属性。

下面是一个简单的 Java 实体:

public class MyDto {

    private String stringValue;

 

    public MyDto() {

        super();

    }

 

    public String getStringValue() {

        return stringValue;

    }

 

    public void setStringValue(String stringValue) {

        this.stringValue = stringValue;

    }

}

序列化它会得到如下 JSON:

{"stringValue":"some value"}

若想自定义输出,不想用 stringValue,而是 strValue,我们需要简单地为 getter 添加注解:

@JsonProperty("strVal")

public String getStringValue() {

    return stringValue;

}

现在,序列化后,就能得到我们想要的结果:

{"strValue":"some value"}

下面单元测试能证明这点:

@Test

public void givenNameOfFieldIsChanged_whenSerializing_thenCorrect() 

  throws JsonParseException, IOException {

    ObjectMapper mapper = new ObjectMapper();

    MyDtoFieldNameChanged dtoObject = new MyDtoFieldNameChanged();

    dtoObject.setStringValue("a");

 

    String dtoAsString = mapper.writeValueAsString(dtoObject);

 

    assertThat(dtoAsString, not(containsString("stringValue")));

    assertThat(dtoAsString, containsString("strVal"));

}

 

基本 Jackson Unmarshalling


如何把一个 JSON 字符串反序列(deserialize)化成一个 Java 实体。无论 JSON 多么“怪异”,我们需要把它映射成一个预先定义的 Java 实体类。

把 JSON 解析成 JsonNode

如何利用 Jackson 2 把 JSON 字符串转换成一个 com.fasterxml.jackson.databind.JsonNode

  • 快速解析

只需要 ObjectMapper 就可以解析 JSON 字符串:

@Test

public void whenParsingJsonStringIntoJsonNode_thenCorrect() 

  throws JsonParseException, IOException {

    String jsonString = "{\"k1\":\"v1\",\"k2\":\"v2\"}";

 

    ObjectMapper mapper = new ObjectMapper();

    JsonNode actualObj = mapper.readTree(jsonString);

 

    assertNotNull(actualObj);

}

  • 底层解析

出于某种原因,我需要使用底层解析,下面示例展示 JsonParser 负责实际 JSON 字符串的解析:

@Test

public void givenUsingLowLevelApi_whenParsingJsonStringIntoJsonNode_thenCorrect() 

  throws JsonParseException, IOException {

    String jsonString = "{\"k1\":\"v1\",\"k2\":\"v2\"}";

 

    ObjectMapper mapper = new ObjectMapper();

    JsonFactory factory = mapper.getFactory();

    JsonParser parser = factory.createParser(jsonString);

    JsonNode actualObj = mapper.readTree(parser);

 

    assertNotNull(actualObj);

}

  • 使用 JsonNode

JSON 被解析成一个 JsonNode 对象后,我们可以使用 Jackson JSON 树模型:

@Test

public void givenTheJsonNode_whenRetrievingDataFromId_thenCorrect() 

  throws JsonParseException, IOException {

    String jsonString = "{\"k1\":\"v1\",\"k2\":\"v2\"}";

    ObjectMapper mapper = new ObjectMapper();

    JsonNode actualObj = mapper.readTree(jsonString);

 

    // When

    JsonNode jsonNode1 = actualObj.get("k1");

    assertThat(jsonNode1.textValue(), equalTo("v1"));

}

Unmarshalling 带未知属性的 json

用 Jackson 2.x 来 unmarshalling,特别是,如何处理未知属性的 JSON。

  • Unmarshall 一个带额外/未知字段的 json

大多数时候,我们需要把 JSON 映射到预先定义的带很多字段的 Java 对象上。现在,我们简单地忽略掉那些不能被映射到现存 Java 字段的任何 JSON 属性。

例如,我们需要 unmarshall JSON 为下面 Java 实体:

public class MyDto {

 

    private String stringValue;

    private int intValue;

    private boolean booleanValue;

 

    public MyDto() {

        super();

    }

 

    // standard getters and setters and not included

}

 

  • 1,在未知字段上的 UnrecognizedPropertyException 异常

尝试 unmarshall 一个带未知属性的 JSON 到一个简单的 Java 实体,将会导致 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException 异常:

@Test(expected = UnrecognizedPropertyException.class)

public void givenJsonHasUnknownValues_whenDeserializing_thenException()

  throws JsonParseException, JsonMappingException, IOException {

    String jsonAsString = 

        "{\"stringValue\":\"a\"," +

        "\"intValue\":1," +

        "\"booleanValue\":true," +

        "\"stringValue2\":\"something\"}";

    ObjectMapper mapper = new ObjectMapper();

 

    MyDto readValue = mapper.readValue(jsonAsString, MyDto.class);

 

    assertNotNull(readValue);

}

这个失败将会显示如下异常信息:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: 

Unrecognized field "stringValue2" (class com.ln.basicjacksondemo.ignore.MyDto), 

not marked as ignorable (3 known properties: "stringValue", "booleanValue", "intValue"])

  • 2,Dealing with Unknown Fields on the ObjectMapper

我们可以配置 ObjectMapper 来忽略 JSON 中未知属性:

new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

然后,我们就能解析这样的 JSON 到一个 Java 实体:

@Test

public void givenJsonHasUnknownValuesButJacksonIsIgnoringUnknowns_whenDeserializing_thenCorrect()

  throws JsonParseException, JsonMappingException, IOException {

    String jsonAsString = 

        "{\"stringValue\":\"a\"," +

        "\"intValue\":1," +

        "\"booleanValue\":true," +

        "\"stringValue2\":\"something\"}";

    ObjectMapper mapper = 

      new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

 

    MyDto readValue = mapper.readValue(jsonAsString, MyDto.class);

 

    assertNotNull(readValue);

    assertThat(readValue.getStringValue(), equalTo("a"));

    assertThat(readValue.isBooleanValue(), equalTo(true));

    assertThat(readValue.getIntValue(), equalTo(1));

}

  • 3,with Unknown Fields on the Class

我们也可以标记一个类,接受未知字段,而不是在整个 Jackson ObjectMapper:

@JsonIgnoreProperties(ignoreUnknown = true)

public class MyDtoIgnoreUnknown { ... }

现在,测试一下,未知字段会被忽略,只映射能够识别的字段:

@Test

public void givenJsonHasUnknownValuesButIgnoredOnClass_whenDeserializing_thenCorrect() 

  throws JsonParseException, JsonMappingException, IOException {

    String jsonAsString =

        "{\"stringValue\":\"a\"," +

        "\"intValue\":1," +

        "\"booleanValue\":true," +

        "\"stringValue2\":\"something\"}";

    ObjectMapper mapper = new ObjectMapper();

 

    MyDtoIgnoreUnknown readValue = mapper.readValue(jsonAsString, MyDtoIgnoreUnknown.class);

 

    assertNotNull(readValue);

    assertThat(readValue.getStringValue(), equalTo("a"));

    assertThat(readValue.isBooleanValue(), equalTo(true));

    assertThat(readValue.getIntValue(), equalTo(1));

}

 

  • Unmarshall 一个不完整的 json

与未知字段类似,unmarshalling 一个不完整的 JSON – 一个不包含 Java 类中所有字段的 JSON – 对 Jackson 也不是问题:

@Test

public void givenNotAllFieldsHaveValuesInJson_whenDeserializingAJsonToAClass_thenCorrect() 

  throws JsonParseException, JsonMappingException, IOException {

    String jsonAsString = "{\"stringValue\":\"a\",\"booleanValue\":true}";

    ObjectMapper mapper = new ObjectMapper();

 

    MyDto readValue = mapper.readValue(jsonAsString, MyDto.class);

 

    assertNotNull(readValue);

    assertThat(readValue.getStringValue(), equalTo("a"));

    assertThat(readValue.isBooleanValue(), equalTo(true));

}

 

演示


Android 基本 Jackson Marshalling(serialize)/Unmarshalling(deserialize)

图 1 项目结构

  • com.ln.basicjacksondemo.test 包,是单元测试;
  • 其他包,是单元测试需要的相关类;
  • libs 目录,是 Jackson 2 的 Java 包。

 

参考资料


 

术语


Marshalling/Unmarshalling

marshalling 是把一个对象的内存描述转换成适合存储或传输的一个数据格式的过程,它通常用于,数据必须在一个计算机程序的不同部分之间,从一个程序移动到另一个。Marshalling 类似于 serialization,用于一个对象跟远程对象通信,在这种情况下,是一个被序列化的对象。这简化了复杂的通信,使用自定义、复杂的对象通信,而不是基本数据类型。与 marshalling 相对的,或逆过程,称为 unmarshalling(demarshalling,类似于 deserialization)。

在 Python 里,marshal 跟 serialize 是一个概念,但是在 Java 中却不是。

具体参看 http://en.wikipedia.org/wiki/Unmarshalling#Comparison_with_serialization

 

下载 Demo

下载 Jackson 2