Android 高級 Jackson Marshalling(serialize)/Unmarshalling(deserialize)

本文內容

  • 高級 Jackson Marshalling
    • 只序列化符合自定義標準的字段
    • 把 Enums 序列化成 JSON 對象
    • JsonMappingException(沒有找到類的序列化器)
    • Jackson – 自定義序列化器
  • 高級 Jackson Unmarshalling
    • Unmarshall 成 Collection/Array
    • Jackson – 自定義反序列化器
  • 演示
  • 參考資料

本文使用 Jackson 2,包括 jackson-annotations-2.4.0.jar、jackson-core-2.4.1.jar 和 jackson-databind-2.4.1.jar 這三個庫。 html

貌似太理論的東西,看得人也比較少,都喜歡看實際功能的東西,不過啊,只關注功能、理論太弱的人,基本沒前途~java

下載 Demo

下載 Jackson 2

高級 Jackson Marshalling


介紹高級的序列化配置和優化處理條件、各類數據類型以及自定義 Jackson 異常。node

只序列化符合自定義標準的字段

如何使用 Jackson 只序列化一個符合指定的、自定義標準的字段。json

例如,我只想序列化一個正整數,不然,就忽略整個整數。數組

  • 使用 Jackson Filter 控制序列化過程

首先,咱們在實體上用 @JsonFilter 註解定義過濾器:緩存

@JsonFilter("myFilter")
public class MyDto {
    private int intValue;
 
    public MyDto() {
        super();
    }
 
    public int getIntValue() {
        return intValue;
    }
 
    public void setIntValue(int intValue) {
        this.intValue = intValue;
    }
}

而後,定義咱們本身的 PropertyFilter併發

PropertyFilter theFilter = new SimpleBeanPropertyFilter() {
   @Override
   public void serializeAsField
    (Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
     throws Exception {
      if (include(writer)) {
         if (!writer.getName().equals("intValue")) {
            writer.serializeAsField(pojo, jgen, provider);
            return;
         }
         int intValue = ((MyDtoWithFilter) pojo).getIntValue();
         if (intValue >= 0) {
            writer.serializeAsField(pojo, jgen, provider);
         }
      } else if (!jgen.canOmitFields()) { // since 2.3
         writer.serializeAsOmittedField(pojo, jgen, provider);
      }
   }
   @Override
   protected boolean include(BeanPropertyWriter writer) {
      return true;
   }
   @Override
   protected boolean include(PropertyWriter writer) {
      return true;
   }
};

這個 filter 包含一個 intValue 字段是否被序列化的邏輯,這取決於 intValue 的值。app

如今,在 ObjectMapper 裏調用 filter,序列化這個實體:ide

FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);
MyDto dtoObject = new MyDto();
dtoObject.setIntValue(-1);
 
ObjectMapper mapper = new ObjectMapper();
String dtoAsString = mapper.writer(filters).writeValueAsString(dtoObject);

最後,測試 intValue 字段是否在 JSON 輸出中:函數

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

filter 功能很強大,當用 Jackson 序列化複雜對象時,能夠很是靈活地自定義 JSON。

把 Enums 序列化成 JSON 對象

如何用 Jackson 2 把 Java Enum 序列化。

  • 控制枚舉表示

定義下面枚舉:

public enum Type {
    TYPE1(1, "Type A"), TYPE2(2, "Type 2");
 
    private Integer id;
    private String name;
 
    private Type(final Integer id, final String name) {
        this.id = id;
        this.name = name;
    }
 
    // standard getters and setters
}
  • 默認的枚舉表示

默認狀況,Jackson 會把 Java 枚舉表示成一個字符串,例如:

new ObjectMapper().writeValueAsString(Type.TYPE1);

結果:

"TYPE1"

當把這個枚舉序列化成 JSON 對象時,咱們想獲得以下所示:

{"name":"Type A","id":1}
  • 枚舉做爲 Json 對象

用 Jackson 2.1.2 – 下面的配置能夠獲得上面的表示 – 這是經過在枚舉級別上使用 @JsonFormat 註解:

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Type { ... }

當序列化上面枚舉,就會獲得正確的結果:

{"name":"Type A","id":1}
  • 枚舉和 @JsonValue

另一種控制方式是使用 @JsonValue 註解:

public enum TypeEnumWithValue {
    TYPE1(1, "Type A"), TYPE2(2, "Type 2");
 
    private Integer id;
    private String name;
 
    private TypeEnumWithValue(final Integer id, final String name) {
        this.id = id;
        this.name = name;
    }
 
    ...
 
    @JsonValue
    public String getName() {
        return name;
    }
}

咱們在這裏說的是,getName 是此枚舉的實際表示;因此:

String enumAsString = mapper.writeValueAsString(TypeEnumWithValue.TYPE1);
assertThat(enumAsString, is("\"Type A\""));
  • 自定義枚舉序列化器

在 Jackson 2.1.2 之前,或是對枚舉須要更多的自定義 – 咱們能夠使用一個自定義的 Jackson 序列化器 – 首先,咱們須要定義它:

public class TypeSerializer extends JsonSerializer<TypeEnum> {
 
    public void serialize
      (TypeEnumWithCustomSerializer value, JsonGenerator generator, SerializerProvider provider) 
      throws IOException, JsonProcessingException {
        generator.writeStartObject();
        generator.writeFieldName("id");
        generator.writeNumber(value.getId());
        generator.writeFieldName("name");
        generator.writeString(value.getName());
        generator.writeEndObject();
    }
}

咱們如今將綁定序列化器,在類上應用它:

@JsonSerialize(using = TypeSerializer.class)
public enum TypeEnum { ... }

JsonMappingException(沒有找到類的序列化器)

分析序列化沒有 getter 的實體,以及 Jackson JsonMappingException 異常的解決方案。

  • 問題

默認狀況,Jackson 2 運行在 public 或具備 public getter 方法的字段上 – 序列化 private 或 private 包內全部字段的一個實體將會失敗:

ublic class MyDtoNoAccessors {
    String stringValue;
    int intValue;
    boolean booleanValue;
 
    public MyDtoNoAccessors() {
        super();
    }
 
    // no getters
}
@Test(expected = JsonMappingException.class)
public void givenObjectHasNoAccessors_whenSerializing_thenException() 
  throws JsonParseException, IOException {
    String dtoAsString = new ObjectMapper().writeValueAsString(new MyDtoNoAccessors());
 
    assertThat(dtoAsString, notNullValue());
}

異常信息以下所示:

com.fasterxml.jackson.databind.JsonMappingException: 
No serializer found for class dtos.MyDtoNoAccessors 
and no properties discovered to create BeanSerializer 
(to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )
  • 解決方案

顯而易見的解決方案是爲字段添加 getter ,固然,若是實體處於咱們控制之下時。若是不是這種狀況,修改實體的源代碼就是不可能的,那麼 Jackson 爲咱們提供了幾個可選方案。

  • 1,全局自動檢測任何可見的字段

第一個解決方案是,全局配置 ObjectMapper 來檢測全部字段,而無論它們是否可見:

objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

這將容許 private 和 private 包的字段被檢測到,即使沒有 getter,序列化就會正確地執行:

@Test
public void givenObjectHasNoAccessors_whenSerializingWithAllFieldsDetected_thenNoException() 
  throws JsonParseException, IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
    String dtoAsString = objectMapper.writeValueAsString(new MyDtoNoAccessors());
 
    assertThat(dtoAsString, containsString("intValue"));
    assertThat(dtoAsString, containsString("stringValue"));
    assertThat(dtoAsString, containsString("booleanValue"));
}
  • 2,在類的級別上檢測全部字段

Jackson 2 的另外一個選擇是,經過 @JsonAutoDetect 註解,在類的級別上控制字段的可見性,而不是進行全局配置:

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class MyDtoNoAccessors { ... }

對這個註解,序列化不能正確執行:

@Test
public void givenObjectHasNoAccessorsButHasVisibleFields_whenSerializing_thenNoException() 
  throws JsonParseException, IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    String dtoAsString = objectMapper.writeValueAsString(new MyDtoNoAccessors());
 
    assertThat(dtoAsString, containsString("intValue"));
    assertThat(dtoAsString, containsString("stringValue"));
    assertThat(dtoAsString, containsString("booleanValue"));
}

Jackson – 自定義序列化器

如何用 Jackson 2 經過自定義序列化器序列化一個 Java 實體。

  • 標準的序列化

定義兩個簡單實體,如何用 Jackson 序列化它們,不使用任何的自定義邏輯:

public class User {
    public int id;
    public String name;
}
public class Item {
    public int id;
    public String itemName;
    public User owner;
}

如今,序列化 Item 實體(裏邊還包含 User 實體):

Item myItem = new Item(1, "theItem", new User(2, "theUser"));
String serialized = new ObjectMapper().writeValueAsString(myItem);

結果以下:

{
    "id": 1,
    "itemName": "theItem",
    "owner": {
        "id": 2,
        "name": "theUser"
    }
}
  • ObjectMapper 上的自定義序列化器

如今,讓咱們簡化上面的 JSON 輸出,只序列化 Userid,而不是整個 User 對象;咱們但願獲得以下簡單的JSON:

{
    "id": 25,
    "itemName": "FEDUfRgS",
    "owner": 15
}

咱們必須爲 Item 對象定義一個自定義序列化器:

public class ItemSerializer extends JsonSerializer<Item> {
    @Override
    public void serialize(Item value, JsonGenerator jgen, SerializerProvider provider)
      throws IOException, JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("id", value.id);
        jgen.writeStringField("itemName", value.itemName);
        jgen.writeNumberField("owner", value.owner.id);
        jgen.writeEndObject();
    }
}

如今,咱們須要爲 Item 類在 ObjectMapper 註冊這個自定義序列化器,而後,執行序列化:

Item myItem = new Item(1, "theItem", new User(2, "theUser"));
ObjectMapper mapper = new ObjectMapper();
 
SimpleModule module = new SimpleModule();
module.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(module);
 
String serialized = mapper.writeValueAsString(myItem);
  • 在類上自定義序列化器

咱們也能夠在類上直接註冊序列化器,而不用在 ObjectMapper

@JsonSerialize(using = ItemSerializer.class)
public class Item {
    ...
}

如今,當咱們執行標準的序列化時:

Item myItem = new Item(1, "theItem", new User(2, "theUser"));
String serialized = new ObjectMapper().writeValueAsString(myItem);

咱們會獲得自定義的 JSON 輸出,它是由 @JsonSerialize 指定的序列化器建立的:

{
    "id": 25,
    "itemName": "FEDUfRgS",
    "owner": 15
}

 

高級 Jackson Unmarshalling


Unmarshall 成 Collection/Array

如何用 Jackson 2 把一個 JSON 數組(Array )反序列化成一個 Java 數組或集合(Collection )。

  • Unmarshall 成數組(Array)

Jackson 能夠很容易地反序列化成一個 Java 數組:

@Test
public void givenJsonArray_whenDeserializingAsArray_thenCorrect() 
  throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();
    List<MyDto> listOfDtos = Lists.newArrayList(
      new MyDto("a", 1, true), new MyDto("bc", 3, false));
    String jsonArray = mapper.writeValueAsString(listOfDtos);
    // [{"stringValue":"a","intValue":1,"booleanValue":true},
    // {"stringValue":"bc","intValue":3,"booleanValue":false}]
 
    MyDto[] asArray = mapper.readValue(jsonArray, MyDto[].class);
    assertThat(asArray[0], instanceOf(MyDto.class));
}
  • Unmarshall 成集合(Collection)

把同一個 JSON 數組讀到一個 Java 集合,就有點困難了 – 默認狀況,Jackson 沒法獲得完整的泛型類型信息,而是將建立一個 LinkedHashMap 實例的集合:

@Test
public void givenJsonArray_whenDeserializingAsListWithNoTypeInfo_thenNotCorrect() 
  throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();
 
    List<MyDto> listOfDtos = Lists.newArrayList(
      new MyDto("a", 1, true), new MyDto("bc", 3, false));
    String jsonArray = mapper.writeValueAsString(listOfDtos);
 
    List<MyDto> asList = mapper.readValue(jsonArray, List.class);
    assertThat((Object) asList.get(0), instanceOf(LinkedHashMap.class));
}

有兩種方式能夠幫助 Jackson 理解正確的類型信息 – 咱們或是使用 TypeReference

@Test
public void givenJsonArray_whenDeserializingAsListWithTypeReferenceHelp_thenCorrect() 
  throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();
 
    List<MyDto> listOfDtos = Lists.newArrayList(
      new MyDto("a", 1, true), new MyDto("bc", 3, false));
    String jsonArray = mapper.writeValueAsString(listOfDtos);
 
    List<MyDto> asList = mapper.readValue(jsonArray, new TypeReference<List<MyDto>>() { });
    assertThat(asList.get(0), instanceOf(MyDto.class));
}

或是使用接受 JavaTypereadValue 的重載方法:

@Test
public final void givenJsonArray_whenDeserializingAsListWithJavaTypeHelp_thenCorrect() 
  throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();
 
    List<MyDto> listOfDtos = Lists.newArrayList(
      new MyDto("a", 1, true), new MyDto("bc", 3, false));
    String jsonArray = mapper.writeValueAsString(listOfDtos);
 
    final CollectionType javaType = 
      mapper.getTypeFactory().constructCollectionType(List.class, MyDto.class);
    List<MyDto> asList = mapper.readValue(jsonArray, javaType);
    assertThat(asList.get(0), instanceOf(MyDto.class));
}

最後,須要注意的是,MyDto 類須要一個沒有任何參數的默認構造函數,不然,Jackson 將不能實例化:

com.fasterxml.jackson.databind.JsonMappingException: 
No suitable constructor found for type [simple type, class org.baeldung.jackson.ignore.MyDto]: 
can not instantiate from JSON object (need to add/enable type information?)

Jackson – 自定義反序列化器

如何用 Jackson 2 經過一個自定義反序列化器來反序列化 JSON。

標準的反序列化

定義兩個實體,如何用 Jackson 2 反序列化成一個 JSON 表示,不用任何的自定義:

public class User {
    public int id;
    public String name;
}
public class Item {
    public int id;
    public String itemName;
    public User owner;
}

如今,咱們定義反序列化後想要獲得的 JSON 表示:

{
    "id": 1,
    "itemName": "theItem",
    "owner": {
        "id": 2,
        "name": "theUser"
    }
}

最後,把這個 JSON unmarshall 成 Java 實體:

Item itemWithOwner = new ObjectMapper().readValue(json, Item.class);

在 ObjectMapper 上自定義反序列化器

在前面的示例中,JSON 表示與 Java 實體徹底匹配。下面,咱們簡化 JSON:

{
    "id": 1,
    "itemName": "theItem",
    "createdBy": 2
}

當把這個 JSON unmarshalling 成相同的實體時,默認狀況,將會失敗:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "createdBy" (class org.baeldung.jackson.dtos.Item),
not marked as ignorable (3 known properties: "id", "owner", "itemName"])
 at [Source: java.io.StringReader@53c7a917; line: 1, column: 43]
 (through reference chain: org.baeldung.jackson.dtos.Item["createdBy"])

如今,咱們用自定義的反序列化器來反序列化:

public class ItemDeserializer extends JsonDeserializer<Item> {
 
    @Override
    public Item deserialize(JsonParser jp, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        int id = (Integer) ((IntNode) node.get("id")).numberValue();
        String itemName = node.get("itemName").asText();
        int userId = (Integer) ((IntNode) node.get("createdBy")).numberValue();
 
        return new Item(id, itemName, new User(userId, null));
    }
}

反序列化使用 JSON 的標準 Jackson 表示 – JsonNode。一旦輸入的 JSON 被表示成一個 JsonNode,就能夠從它提取相關的信息,並構造本身的 Item 實體。

咱們須要註冊這個自定義的反序列化器,再正常地反序列化 JSON:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new ItemDeserializer());
mapper.registerModule(module);
 
Item readValue = mapper.readValue(json, Item.class);

在類的級別上自定義反序列化器

另外一個方法是,也能夠直接在類上註冊反序列化器:

@JsonDeserialize(using = ItemDeserializer.class)
public class Item {
    ...
}

用這個定義在類級別上的反序列化器,就不須要在 ObjectMapper 註冊它,那麼一個默認的 mapper 就能夠反序列化:

Item itemWithOwner = new ObjectMapper().readValue(json, Item.class);

 

演示


2014-08-14_142801_副本

圖 1 項目結構

  • libs 裏,除了 jackson 2 的三個包外,還有 google guava 的一個包,是 Java 項目普遍依賴的核心庫,如集合 [collections] 、緩存 [caching] 、原生類型支持 [primitives support] 、併發庫 [concurrency libraries] 、通用註解 [common annotations] 、字符串處理 [string processing] 、I/O 等等。

 

參考資料


 

下載 Demo

下載 Jackson 2

相關文章
相關標籤/搜索