• 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 这三个库。

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

    下载 Demo

    下载 Jackson 2

    高级 Jackson Marshalling


    介绍高级的序列化配置和优化处理条件、各种数据类型以及自定义 Jackson 异常。

    只序列化符合自定义标准的字段

    如何使用 Jackson 只序列化一个符合指定的、自定义标准的字段。

    例如,我只想序列化一个正整数,否则,就忽略整个整数。

    • 使用 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 的值。

    现在,在 ObjectMapper 里调用 filter,序列化这个实体:

    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

  • 相关阅读:
    Intent.ACTION_TIME_TICK 广播
    Android ContentObserver
    android:duplicateParentState属性解释
    Android CursorAdapter
    android AndroidManifest.xml 多个android.intent.action.MAIN (
    PreferenceActivity详解
    WORD和WPS中英文混合的内容间距离很大怎么办?
    Android 屏幕适配
    OC第四课
    PL/SQL联系oracle成功可以sql解决的办法是检查表的名称无法显示
  • 原文地址:https://www.cnblogs.com/liuning8023/p/3909788.html
Copyright © 2020-2023  润新知