分类
Data Jackson Json

初步了解Jackson对象映射器

译者注

原文

https://www.baeldung.com/jackson-object-mapper-tutorial

Demo

https://github.com/eugenp/tutorials/tree/master/jackson-simple

1. 前言

本文致力于帮助读者理解Jackson对象映射器(ObjectMapper),以及学习如何把对象序列化为JSON、如何把JSON反序列化成Java对象。

如果希望了解更多Jackson库的知识,请点击Jackson入门教程

2. 依赖

对于maven托管的项目,我们首先在pom.xml中增加下面的依赖信息:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>

这个依赖递归的将以下两个库添加到类路径中:

  1. jackson-annotations
  2. jackson-core

我们只需要使用Maven中心仓库的jackson-databind中的最新版本。

3. 使用对象映射器进行读写

我们从最简单的读写操作开始。
对象映射器的简单读取API是一个很好的切入点,我们可以用它来分析JSON内容,或者把JSON反序列化成Java对象。
另一方面,我们可以使用读取值API来把Java对象序列化成JSON格式的输出。
我们将会使用带有两个字段的“汽车”作为示例代码,来演示序列化和反序列化功能:

public class Car {

    private String color;
    private String type;

    // standard getters setters
}

3.1 Java 对象到 JSON

先来看第一个示例,使用对象映射器的写方法来实现Java对象到JSON:

ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow", "renault");
objectMapper.writeValue(new File("target/car.json"), car);

上面代码输出的信息将会是:

{"color":"yellow","type":"renault"}

对象映射器的writeValueAsString()方法和writeValueAsBytes()方法会从Java对象生成JSON,并且以字符串或二进制数组的方式返回生成的JSON:

String carAsString = objectMapper.writeValueAsString(car);

3.2 JSON到Java对象

在这个示例中,使用对象映射器类来把JSON字符串转化为Java对象:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Car car = objectMapper.readValue(json, Car.class);

这个读方法也支持其他输入形式,如文件:

Car car = objectMapper.readValue(new File("src/test/resources/json_car.json"), Car.class);

或者URL:

Car car = objectMapper.readValue(new URL("file:src/test/resources/json_car.json"), Car.class);

3.3 JSON 到 Jackson JsonNode

除了转化为对象,我们也可以把JSON数据转化为JSON节点对象,这样就可以在不创建具体类的情况下,直接从某个JSON对象中获得某个字段的数据:

String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }";
JsonNode jsonNode = objectMapper.readTree(json);
String color = jsonNode.get("color").asText();
// Output: color -> Black

3.4 根据JSON数组字符串创建Java列表

我们可以使用类型引用(TypeReference),把JSON从数组格式转化为Java对象列表:

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";

List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});

3.5 从JSON字符串创建Java映射(Map)

相似的,我们可以把JSON解析成一个Java映射:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";

Map<String, Object> map = objectMapper.readValue(json, new TypeReference<Map<String,Object>>(){});

4. 高级特性

Jackson库的强大功能之一,就是高度自由定制的序列化和反序列化方法。
在这个版块,我们将会学习一些高级特性,它允许你输入(或输出)的JSON和生成(或消耗)的对象不同。

4.1 配置序列化和反序列化特性

当我们把JSON对象转化为Java类时,如果JSON字符串有一些新字段,在默认设置下会导致异常:

String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" : \"1970\" }";

上面示例中的JSON字符串,如果使用默认的解析设置,在转化为Java对象Car时就会出现“未识别的属性异常”(UnrecognizedPropertyException)。
通过配置方法,我们可以扩展默认的进程,实现忽略空字段。

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Car car = objectMapper.readValue(jsonString, Car.class);

JsonNode jsonNodeRoot = objectMapper.readTree(jsonString);
JsonNode jsonNodeYear = jsonNodeRoot.get("year");
String year = jsonNodeYear.asText();

另一个基于FAIL_ON_NULL_FOR_PRIMITIVES的选项定义了“是否允许主键空值”:

objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);

相似地,FAIL_ON_NUMBERS_FOR_ENUM选项控制“枚举值是否允许序列化、反序列化成数字”

objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false);

你可以在官方网站中找到序列化和反序列化的详尽表单

4.2 创建用户自定义的序列化器和反序列化器

对象映射器类的另一个重要特性是:允许注册一个用户自定义的序列化器和反序列化器。
自定义的序列化器和反序列化器在某些情况下非常有用,比如输入或输出的JSON和需要比序列化或反序列化的JAVA类结构不同。
下面是一个自定义JSON序列化器的例子:

public class CustomCarSerializer extends StdSerializer<Car> {

    public CustomCarSerializer() {
        this(null);
    }

    public CustomCarSerializer(Class<Car> t) {
        super(t);
    }

    @Override
    public void serialize(
      Car car, JsonGenerator jsonGenerator, SerializerProvider serializer) {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("car_brand", car.getType());
        jsonGenerator.writeEndObject();
    }
}

这个自定义的序列化器可以像这样被调用:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = 
  new SimpleModule("CustomCarSerializer", new Version(1, 0, 0, null, null, null));
module.addSerializer(Car.class, new CustomCarSerializer());
mapper.registerModule(module);
Car car = new Car("yellow", "renault");
String carJson = mapper.writeValueAsString(car);

此时,客户端将会收到类似这样的信息:

var carJson = {"car_brand":"renault"}

接下来,这是一个自定义JSON反序列化的例子:

public class CustomCarDeserializer extends StdDeserializer<Car> {

    public CustomCarDeserializer() {
        this(null);
    }

    public CustomCarDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Car deserialize(JsonParser parser, DeserializationContext deserializer) {
        Car car = new Car();
        ObjectCodec codec = parser.getCodec();
        JsonNode node = codec.readTree(parser);

        // try catch block
        JsonNode colorNode = node.get("color");
        String color = colorNode.asText();
        car.setColor(color);
        return car;
    }
}

这个自定义的反序列化可以像这样被调用:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
ObjectMapper mapper = new ObjectMapper();
SimpleModule module =
  new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(Car.class, new CustomCarDeserializer());
mapper.registerModule(module);
Car car = mapper.readValue(json, Car.class);

4.3 处理日期格式

java.util.Date这个类提供的默认的序列化器,在处理日期时会提供数字形式,例如时间戳(时间戳是一个数字,UTC时间是从1970年1月1日开始经过的毫秒数)。但是这种格式对于用户的可读性比较长,并且需要进一步转化格式才能变成用户可以阅读的格式。
我们把刚才用到的这个Car类,包装到一个带有datePurchased属性的Request类中(装饰器模式):

public class Request 
{
    private Car car;
    private Date datePurchased;

    // standard getters setters
}

为了控制字符串格式,比如设置为“yyyy-MM-dd HH:mm” (年-月-日 时:分),可以使用下面的代码:

ObjectMapper objectMapper = new ObjectMapper();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
objectMapper.setDateFormat(df);
String carAsString = objectMapper.writeValueAsString(request);
// output: {"car":{"color":"yellow","type":"renault"},"datePurchased":"2016-07-03 11:43 AM CEST"}

如果要了解更多使用Jackson序列化时间的方法,请点击链接

4.4 处理集合

DeserializationFeature类提供了另一个很小但是很有用的特性,那就是允许从JSON数组生成我们想要的类型的集合。
例如,我们可以生成这样一个数组类型的结果:

String jsonCarArray = 
  "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class);
// print cars

或者列表形式:

String jsonCarArray = 
  "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});
// print cars

这里有用Jackson处理集合的更多信息。

5. 结论

Jackson是一个成熟稳定的JSON序列化、反序列化的Java库。对象映射器API提供了直接的方式,去灵活的解析和生成JSON对象的方式。
本文讨论了一些知名度非常高的主要特性。
源代码在Github上提供。

分类
Data Jackson Json

忽略Jackson的空字段

译者注

原文:

https://www.baeldung.com/jackson-ignore-null-fields

官方Demo:

https://github.com/eugenp/tutorials/tree/master/jackson-simple

1. 前言

在本文中我们将会学习,在序列化一个Java类时,如何通过设置Jackson,来实现忽略空字段

如果想要获得更深入了解Jackson的其他好玩的用法,请点击Jackson基本教程

2. 在类中忽略空字段

我们可以使用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
}

现在我们进行测试就会发现,空值确实没有出现在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")));
}

3. 全局忽略空字段

Jackson还可以通过对象映射表(ObjectMapper)来全局的配置空字段。

mapper.setSerializationInclusion(Include.NON_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")));
}

4. 结论

“忽略空字段”是Jackson常见的一个基本配置,通常我们需要更好地控制JSON输出,本文演示了如何通过它来控制类。
此外,还有更多的高级用法,例如“当序列化一个映射时忽略空字段

Github上可以找到本文的示例代码。