电子书 – Spring Cloud 指南 – NPI EA (分类=Spring Cloud)
announcement - icon

让我们开始使用 Spring Cloud 的微服务架构

>> 加入 Pro 并下载电子书

电子书 – Mockito – NPI EA (标签 = Mockito)
announcement - icon

模拟是单元测试的重要组成部分,Mockito 库使编写 清晰直观的单元测试 变得容易,用于您的 Java 代码。

通过我们的 Mockito 指南 开始模拟,并改进您的应用程序测试

下载电子书

电子书 – Java 并发 – NPI EA (分类=Java 并发)
announcement - icon

在应用程序中处理并发可能是一个棘手的过程,其中包含许多 潜在的陷阱。 扎实的掌握基本知识将有助于最大程度地减少这些问题。

通过我们的 Java 并发 指南开始了解多线程应用程序

>> 下载电子书

电子书 – 响应式 – NPI EA (分类=响应式)
announcement - icon

Spring 5 增加了对使用 Spring WebFlux 模块进行响应式编程的支持,此支持自那时起不断改进。 开始使用 Reactor 项目基础知识和 Spring Boot 中的响应式编程

>> 加入 Pro 并下载电子书

电子书 – Java Streams – NPI EA (分类=Java Streams)
announcement - icon

自从 Java 8 引入以来,Stream API 已成为 Java 开发的基础。 基本操作,例如迭代、过滤、映射元素序列,使用起来看似很简单。

但这些也可能被过度使用并陷入一些常见陷阱。

更好地了解 Stream 的工作方式 以及如何将其与其他语言功能结合使用,请查看我们关于 Java Streams 的指南

>> 加入 Pro 并下载电子书

电子书 – Jackson – NPI EA (分类=Jackson)
announcement - icon

用 Jackson 正确处理 JSON

下载电子书

电子书 – HTTP 客户端 – NPI EA (分类=Http 客户端)
announcement - icon

充分利用 Apache HTTP 客户端

下载电子书

电子书 – Maven – NPI EA (分类 = Maven)
announcement - icon

开始使用 Apache Maven

下载电子书

电子书 – 持久化 – NPI EA (分类=持久化)
announcement - icon

您在努力实现正确的持久化层 Spring 吗?

探索电子书

电子书 – RwS – NPI EA (分类=Spring MVC)
announcement - icon

使用 Spring 构建 REST API 吗?

下载电子书

课程 – LS – NPI EA (分类=Jackson)
announcement - icon

通过 Learn Spring 课程开始学习 Spring 和 Spring Boot

>> 学习 SPRING
课程 – RWSB – NPI EA (分类=REST)
announcement - icon

通过构建一个完整的 REST API,深入了解 Spring Boot 3 和 Spring 6,使用该框架

>> 全新的“REST With Spring Boot”

课程 – LSS – NPI EA (分类=Spring Security)
announcement - icon

是的,Spring Security 可能很复杂,从核心内的更高级功能到框架中深入的 OAuth 支持。

我将安全材料构建为 两个完整的课程 - 核心和 OAuth,以针对这些更复杂的场景进行实践。 我们探索何时以及如何使用每个功能,并 在后台项目中对其进行编码

您可以在这里探索该课程

>> 学习 Spring Security

课程 – LSD – NPI EA (标签=Spring Data JPA)
announcement - icon

Spring Data JPA 是处理 JPA 复杂性的绝佳方式,它具有 Spring Boot 的强大简洁性

通过引导式参考课程开始使用 Spring Data JPA

>> 查看课程

合作伙伴 – Moderne – NPI EA (类别=Spring Boot)
announcement - icon

使用 OpenRewrite 安全且自动地重构 Java 代码。

手动重构大型代码库既缓慢、有风险,又容易拖延。OpenRewrite 应运而生。这个用于大规模、自动化代码转换的开源框架可以帮助团队安全、一致地进行现代化改造。

每个月,OpenRewrite 的创建者和维护者 Moderne 都会举办现场、实践培训课程——一个面向初学者,一个面向经验丰富的用户。您将了解配方的运作方式、如何将其应用于项目,以及如何自信地进行代码现代化改造。

参加下一次课程,带来您的问题,并学习如何自动化通常会占用您 sprint 时间的工作。

合作伙伴 – LambdaTest – NPI EA (类别=测试)
announcement - icon

回归测试是发布流程中的重要步骤,以确保新代码不会破坏现有功能。随着代码库的不断发展,我们希望频繁运行这些测试,以便尽早发现任何问题。

确保这些测试以自动化的方式频繁运行的最佳方法当然是将其包含在 CI/CD 管道中。 这样,每次向仓库提交代码时,回归测试将自动执行。

在本教程中,我们将学习如何使用 Selenium 创建回归测试,然后使用 GitHub Actions 将它们包含在我们的管道中,在 LambdaTest 云网格上运行

>> 如何使用 GitHub Actions 运行 Selenium 回归测试

课程 – LJB – NPI EA (类别 = Core Java)
announcement - icon

通过编码方式构建 Java 的坚实、实用的基础

>> 学习 Java 基础

电子书 – Jackson – NPI (分类=Jackson)
announcement - icon

Jackson 和 JSON 在 Java 中,最终通过实践学习的方式掌握

>> 下载电子书

1. 概述

在本教程中,我们将深入研究 Jackson 注解

我们将学习如何使用现有的注解,如何创建自定义注解,以及最后如何禁用它们。

更多阅读

更多 Jackson 注解

本文涵盖了一些鲜为人知的 Jackson 提供的 JSON 处理注解。

Jackson - 双向关系

如何使用 Jackson 打破双向关系中的无限递归问题。

开始使用 Jackson 中的自定义反序列化

使用 Jackson 将自定义 JSON 映射到任何 java 实体图,完全控制反序列化过程。

2. Jackson 序列化注解

首先,我们将看一下序列化注解。

2.1. @JsonAnyGetter

@JsonAnyGetter 注解允许使用 Map 字段作为标准属性,从而提供灵活性。

例如,ExtendableBean 实体具有 name 属性和一组以键/值对形式存在的可扩展属性

public class ExtendableBean {
    public String name;
    private Map<String, String> properties;

    @JsonAnyGetter
    public Map<String, String> getProperties() {
        return properties;
    }
}

当我们序列化此实体的实例时,我们将获得 Map 中的所有键值对作为标准的、简单的属性

{
    "name":"My bean",
    "attr2":"val2",
    "attr1":"val1"
}

以下是此实体序列化的实践方式

@Test
public void whenSerializingUsingJsonAnyGetter_thenCorrect()
  throws JsonProcessingException {
 
    ExtendableBean bean = new ExtendableBean("My bean");
    bean.add("attr1", "val1");
    bean.add("attr2", "val2");

    String result = new ObjectMapper().writeValueAsString(bean);
 
    assertThat(result, containsString("attr1"));
    assertThat(result, containsString("val1"));
}

我们还可以使用可选参数 enabled 作为 false 来禁用 @JsonAnyGetter()。在这种情况下,Map 将被转换为 JSON,并在序列化后显示在 properties 变量下。

2.2. @JsonGetter

@JsonGetter 注解是 @JsonProperty 注解的替代方案,它将方法标记为 getter 方法。

在下面的示例中,我们将方法 getTheName() 指定为 MyBean 实体 name 属性的 getter 方法

public class MyBean {
    public int id;
    private String name;

    @JsonGetter("name")
    public String getTheName() {
        return name;
    }
}

以下是它的实践方式

@Test
public void whenSerializingUsingJsonGetter_thenCorrect()
  throws JsonProcessingException {
 
    MyBean bean = new MyBean(1, "My bean");

    String result = new ObjectMapper().writeValueAsString(bean);
 
    assertThat(result, containsString("My bean"));
    assertThat(result, containsString("1"));
}

2.3. @JsonPropertyOrder

我们可以使用 @JsonPropertyOrder 注解来指定 序列化时属性的顺序

让我们为 MyBean 实体的属性设置一个自定义顺序

@JsonPropertyOrder({ "name", "id" })
public class MyBean {
    public int id;
    public String name;
}

以下是序列化的输出

{
    "name":"My bean",
    "id":1
}

然后我们可以进行一个简单的测试

@Test
public void whenSerializingUsingJsonPropertyOrder_thenCorrect()
  throws JsonProcessingException {
 
    MyBean bean = new MyBean(1, "My bean");

    String result = new ObjectMapper().writeValueAsString(bean);
    assertThat(result, containsString("My bean"));
    assertThat(result, containsString("1"));
}

我们还可以使用 @JsonPropertyOrder(alphabetic=true) 按字母顺序对属性进行排序。在这种情况下,序列化的输出将是

{
    "id":1,
    "name":"My bean"
}

2.4. @JsonRawValue

@JsonRawValue 注解可以 指示 Jackson 将属性完全按照原样序列化

在下面的示例中,我们使用 @JsonRawValue 将一些自定义 JSON 作为实体的值嵌入

public class RawBean {
    public String name;

    @JsonRawValue
    public String json;
}

序列化实体后的输出是

{
    "name":"My bean",
    "json":{
        "attr":false
    }
}

接下来,这是一个简单的测试

@Test
public void whenSerializingUsingJsonRawValue_thenCorrect()
  throws JsonProcessingException {
 
    RawBean bean = new RawBean("My bean", "{\"attr\":false}");

    String result = new ObjectMapper().writeValueAsString(bean);
    assertThat(result, containsString("My bean"));
    assertThat(result, containsString("{\"attr\":false}"));
}

我们还可以使用可选的布尔参数 value,该参数定义此注解是否处于活动状态。

2.5. @JsonValue

@JsonValue 指示库将用于序列化整个实例的单个方法。

例如,在枚举中,我们使用 @JsonValue 注解 getName,以便任何此类实体都通过其名称进行序列化

public enum TypeEnumWithValue {
    TYPE1(1, "Type A"), TYPE2(2, "Type 2");

    private Integer id;
    private String name;

    // standard constructors

    @JsonValue
    public String getName() {
        return name;
    }
}

现在这是我们的测试

@Test
public void whenSerializingUsingJsonValue_thenCorrect()
  throws JsonParseException, IOException {
 
    String enumAsString = new ObjectMapper()
      .writeValueAsString(TypeEnumWithValue.TYPE1);

    assertThat(enumAsString, is(""Type A""));
}

2.6. @JsonRootName

如果启用了包装,则使用 @JsonRootName 注解来指定要使用的根包装器的名称。

包装意味着,而不是将 User 序列化为如下内容

{
    "id": 1,
    "name": "John"
}

它将被这样包装

{
    "User": {
        "id": 1,
        "name": "John"
    }
}

所以让我们看一个例子。我们将会使用 @JsonRootName 注解来指示这个潜在的包装实体名称

@JsonRootName(value = "user")
public class UserWithRoot {
    public int id;
    public String name;
}

默认情况下,包装器的名称将是类的名称 – UserWithRoot。通过使用该注解,我们可以得到更简洁的user:

@Test
public void whenSerializingUsingJsonRootName_thenCorrect()
  throws JsonProcessingException {
 
    UserWithRoot user = new User(1, "John");

    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
    String result = mapper.writeValueAsString(user);

    assertThat(result, containsString("John"));
    assertThat(result, containsString("user"));
}

以下是序列化的输出

{
    "user":{
        "id":1,
        "name":"John"
    }
}

自 Jackson 2.4 起,一个新的可选参数 namespace 可用于 XML 等数据格式。如果添加它,它将成为完全限定名称的一部分

@JsonRootName(value = "user", namespace="users")
public class UserWithRootNamespace {
    public int id;
    public String name;

    // ...
}

如果使用 XmlMapper 进行序列化,输出将会是

<user xmlns="users">
    <id xmlns="">1</id>
    <name xmlns="">John</name>
    <items xmlns=""/>
</user>

2.7. @JsonSerialize

@JsonSerialize 指示在序列化实体时要使用的自定义序列化器。

让我们看一个简单的例子。我们将使用 @JsonSerialize 来使用 CustomDateSerializer 序列化 eventDate 属性

public class EventWithSerializer {
    public String name;

    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;
}

这是简单的自定义 Jackson 序列化器

public class CustomDateSerializer extends StdSerializer<Date> {

    private static SimpleDateFormat formatter 
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateSerializer() { 
        this(null); 
    } 

    public CustomDateSerializer(Class<Date> t) {
        super(t); 
    }

    @Override
    public void serialize(
      Date value, JsonGenerator gen, SerializerProvider arg2) 
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.format(value));
    }
}

现在让我们在测试中使用它们

@Test
public void whenSerializingUsingJsonSerialize_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    EventWithSerializer event = new EventWithSerializer("party", date);

    String result = new ObjectMapper().writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

3. Jackson 反序列化注解

接下来,我们来探讨 Jackson 反序列化注解。

3.1. @JsonCreator

我们可以使用 @JsonCreator 注解来调整反序列化时使用的构造函数/工厂。

当我们需要反序列化与目标实体不完全匹配的 JSON 时,它非常有用。

让我们看一个例子。假设我们需要反序列化以下 JSON

{
    "id":1,
    "theName":"My bean"
}

然而,我们的目标实体中没有 theName 字段,只有 name 字段。现在我们不想更改实体本身,我们只需要通过使用 @JsonCreator 注解以及 @JsonProperty 注解来对反序列化过程进行更多的控制

public class BeanWithCreator {
    public int id;
    public String name;

    @JsonCreator
    public BeanWithCreator(
      @JsonProperty("id") int id, 
      @JsonProperty("theName") String name) {
        this.id = id;
        this.name = name;
    }
}

让我们实际看一下

@Test
public void whenDeserializingUsingJsonCreator_thenCorrect()
  throws IOException {
 
    String json = "{\"id\":1,\"theName\":\"My bean\"}";

    BeanWithCreator bean = new ObjectMapper()
      .readerFor(BeanWithCreator.class)
      .readValue(json);
    assertEquals("My bean", bean.name);
}

3.2. @JacksonInject

@JacksonInject 指示属性的值将从注入获取,而不是从 JSON 数据获取。

在下面的示例中,我们使用 @JacksonInject 来注入属性 id

public class BeanWithInject {
    @JacksonInject
    public int id;
    
    public String name;
}

它的工作方式如下

@Test
public void whenDeserializingUsingJsonInject_thenCorrect()
  throws IOException {
 
    String json = "{\"name\":\"My bean\"}";
    
    InjectableValues inject = new InjectableValues.Std()
      .addValue(int.class, 1);
    BeanWithInject bean = new ObjectMapper().reader(inject)
      .forType(BeanWithInject.class)
      .readValue(json);
    
    assertEquals("My bean", bean.name);
    assertEquals(1, bean.id);
}

3.3. @JsonAnySetter

@JsonAnySetter 允许我们使用 Map 作为标准属性,从而获得灵活性。在反序列化时,JSON 中的属性将简单地添加到 map 中。

首先,我们将使用 @JsonAnySetter 来反序列化实体 ExtendableBean

public class ExtendableBean {
    public String name;
    private Map<String, String> properties;

    @JsonAnySetter
    public void add(String key, String value) {
        properties.put(key, value);
    }
}

这是我们需要反序列化的 JSON

{
    "name":"My bean",
    "attr2":"val2",
    "attr1":"val1"
}

然后,这是它们如何结合在一起

@Test
public void whenDeserializingUsingJsonAnySetter_thenCorrect()
  throws IOException {
    String json
      = "{\"name\":\"My bean\",\"attr2\":\"val2\",\"attr1\":\"val1\"}";

    ExtendableBean bean = new ObjectMapper()
      .readerFor(ExtendableBean.class)
      .readValue(json);
    
    assertEquals("My bean", bean.name);
    assertEquals("val2", bean.getProperties().get("attr2"));
}

3.4. @JsonSetter

@JsonSetter@JsonProperty 的替代方案,它将方法标记为 setter 方法。

当我们需要读取一些 JSON 数据时,这非常有用,但是 目标实体类与该数据不完全匹配,因此我们需要调整过程使其适应。

在下面的示例中,我们将指定方法 setTheName() 作为我们的 MyBean 实体中 name 属性的 setter

public class MyBean {
    public int id;
    private String name;

    @JsonSetter("name")
    public void setTheName(String name) {
        this.name = name;
    }
}

现在,当我们需要反序列化一些 JSON 数据时,这可以完美地工作

@Test
public void whenDeserializingUsingJsonSetter_thenCorrect()
  throws IOException {
 
    String json = "{\"id\":1,\"name\":\"My bean\"}";

    MyBean bean = new ObjectMapper()
      .readerFor(MyBean.class)
      .readValue(json);
    assertEquals("My bean", bean.getTheName());
}

3.5. @JsonDeserialize

@JsonDeserialize 指示使用自定义反序列化器。

首先,我们将使用 @JsonDeserialize 使用 CustomDateDeserializer 来反序列化 eventDate 属性

public class EventWithSerializer {
    public String name;

    @JsonDeserialize(using = CustomDateDeserializer.class)
    public Date eventDate;
}

这是自定义反序列化器

public class CustomDateDeserializer
  extends StdDeserializer<Date> {

    private static SimpleDateFormat formatter
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateDeserializer() { 
        this(null); 
    } 

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

    @Override
    public Date deserialize(
      JsonParser jsonparser, DeserializationContext context) 
      throws IOException {
        
        String date = jsonparser.getText();
        try {
            return formatter.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

接下来,这是紧密结合的测试

@Test
public void whenDeserializingUsingJsonDeserialize_thenCorrect()
  throws IOException {
 
    String json
      = "{"name":"party","eventDate":"20-12-2014 02:30:00"}";

    SimpleDateFormat df
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    EventWithSerializer event = new ObjectMapper()
      .readerFor(EventWithSerializer.class)
      .readValue(json);
    
    assertEquals(
      "20-12-2014 02:30:00", df.format(event.eventDate));
}

3.6. @JsonAlias

@JsonAlias 定义了在反序列化期间,属性的一个或多个替代名称

让我们看一个快速示例,了解此注解的工作方式

public class AliasBean {
    @JsonAlias({ "fName", "f_name" })
    private String firstName;   
    private String lastName;
}

我们有一个 POJO,并且想将类似 fNamef_namefirstName 的 JSON 值反序列化到 POJO 的 firstName 变量中。

下面是一个测试,确保此注解按预期工作

@Test
public void whenDeserializingUsingJsonAlias_thenCorrect() throws IOException {
    String json = "{\"fName\": \"John\", \"lastName\": \"Green\"}";
    AliasBean aliasBean = new ObjectMapper().readerFor(AliasBean.class).readValue(json);
    assertEquals("John", aliasBean.getFirstName());
}

4. Jackson 属性包含注解

4.1. @JsonIgnoreProperties

@JsonIgnoreProperties 是一个类级注解,它标记 Jackson 将忽略的属性或属性列表。

让我们看一个快速示例,在序列化时忽略属性 id

@JsonIgnoreProperties({ "id" })
public class BeanWithIgnore {
    public int id;
    public String name;
}

现在这是测试,确保忽略生效

@Test
public void whenSerializingUsingJsonIgnoreProperties_thenCorrect()
  throws JsonProcessingException {
 
    BeanWithIgnore bean = new BeanWithIgnore(1, "My bean");

    String result = new ObjectMapper()
      .writeValueAsString(bean);
    
    assertThat(result, containsString("My bean"));
    assertThat(result, not(containsString("id")));
}

要忽略 JSON 输入中的任何未知属性,而不会抛出异常,我们可以设置 @JsonIgnoreProperties  注解的 ignoreUnknown=true

4.2. @JsonIgnore

相反,@JsonIgnore 注解用于标记在字段级别要忽略的属性。

让我们使用 @JsonIgnore 忽略序列化中的属性 id

public class BeanWithIgnore {
    @JsonIgnore
    public int id;

    public String name;
}

然后我们将进行测试,以确保 id 已成功忽略

@Test
public void whenSerializingUsingJsonIgnore_thenCorrect()
  throws JsonProcessingException {
 
    BeanWithIgnore bean = new BeanWithIgnore(1, "My bean");

    String result = new ObjectMapper()
      .writeValueAsString(bean);
    
    assertThat(result, containsString("My bean"));
    assertThat(result, not(containsString("id")));
}

4.3. @JsonIgnoreType

@JsonIgnoreType 标记已注解类型的所有属性都将被忽略。

我们可以使用该注解来标记类型 Name 的所有属性,使其被忽略

public class User {
    public int id;
    public Name name;

    @JsonIgnoreType
    public static class Name {
        public String firstName;
        public String lastName;
    }
}

我们还可以进行测试以确保忽略正常工作

@Test
public void whenSerializingUsingJsonIgnoreType_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    User.Name name = new User.Name("John", "Doe");
    User user = new User(1, name);

    String result = new ObjectMapper()
      .writeValueAsString(user);

    assertThat(result, containsString("1"));
    assertThat(result, not(containsString("name")));
    assertThat(result, not(containsString("John")));
}

4.4. @JsonInclude

我们可以使用 @JsonInclude 来排除具有空值/null/默认值的属性。

让我们看一个在序列化时排除 null 的示例

@JsonInclude(Include.NON_NULL)
public class MyBean {
    public int id;
    public String name;
}

这是完整的测试

public void whenSerializingUsingJsonInclude_thenCorrect()
  throws JsonProcessingException {
 
    MyBean bean = new MyBean(1, null);

    String result = new ObjectMapper()
      .writeValueAsString(bean);
    
    assertThat(result, containsString("1"));
    assertThat(result, not(containsString("name")));
}

4.5. @JsonIncludeProperties

@JsonIncludeProperties 是 Jackson 最受请求的功能之一。它是在 Jackson 2.12 中引入的,可用于标记 Jackson 在序列化和反序列化期间将包含的属性或属性列表。

让我们看一个在序列化时包含属性 name 的快速示例

@JsonIncludeProperties({ "name" })
public class BeanWithInclude {
    public int id;
    public String name;
}

现在这是测试,确保我们只包含 name 属性

@Test
public void whenSerializingUsingJsonIncludeProperties_thenCorrect() throws JsonProcessingException {
    final BeanWithInclude bean = new BeanWithInclude(1, "My bean");
    final String result = new ObjectMapper().writeValueAsString(bean);
    assertThat(result, containsString("My bean"));
    assertThat(result, not(containsString("id")));
    assertThat(result, containsString("name"));
}

4.6. @JsonAutoDetect

@JsonAutoDetect 可以覆盖默认语义,即 哪些属性可见以及哪些不可见

首先,让我们通过一个简单的示例看看该注解如何非常有用;让我们启用序列化私有属性

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class PrivateBean {
    private int id;
    private String name;
}

然后是测试

@Test
public void whenSerializingUsingJsonAutoDetect_thenCorrect()
  throws JsonProcessingException {
 
    PrivateBean bean = new PrivateBean(1, "My bean");

    String result = new ObjectMapper()
      .writeValueAsString(bean);
    
    assertThat(result, containsString("1"));
    assertThat(result, containsString("My bean"));
}

5. Jackson 多态类型处理注解

接下来让我们看看 Jackson 多态类型处理注解

  • @JsonTypeInfo – 指示在序列化中包含哪些类型信息
  • @JsonSubTypes – 指示已注解类型的子类型
  • @JsonTypeName – 定义用于已注解类的逻辑类型名称

让我们检查一个更复杂的示例,并使用全部三个 – @JsonTypeInfo@JsonSubTypes, @JsonTypeName – 来序列化/反序列化实体 Zoo

public class Zoo {
    public Animal animal;

    @JsonTypeInfo(
      use = JsonTypeInfo.Id.NAME, 
      include = As.PROPERTY, 
      property = "type")
    @JsonSubTypes({
        @JsonSubTypes.Type(value = Dog.class, name = "dog"),
        @JsonSubTypes.Type(value = Cat.class, name = "cat")
    })
    public static class Animal {
        public String name;
    }

    @JsonTypeName("dog")
    public static class Dog extends Animal {
        public double barkVolume;
    }

    @JsonTypeName("cat")
    public static class Cat extends Animal {
        boolean likesCream;
        public int lives;
    }
}

当我们进行序列化时

@Test
public void whenSerializingPolymorphic_thenCorrect()
  throws JsonProcessingException {
    Zoo.Dog dog = new Zoo.Dog("lacy");
    Zoo zoo = new Zoo(dog);

    String result = new ObjectMapper()
      .writeValueAsString(zoo);

    assertThat(result, containsString("type"));
    assertThat(result, containsString("dog"));
}

序列化带有 DogZoo 实例的结果如下

{
    "animal": {
        "type": "dog",
        "name": "lacy",
        "barkVolume": 0
    }
}

现在进行反序列化。让我们从以下 JSON 输入开始

{
    "animal":{
        "name":"lacy",
        "type":"cat"
    }
}

然后让我们看看这如何被反序列化成一个 Zoo 实例

@Test
public void whenDeserializingPolymorphic_thenCorrect()
throws IOException {
    String json = "{\"animal\":{\"name\":\"lacy\",\"type\":\"cat\"}}";

    Zoo zoo = new ObjectMapper()
      .readerFor(Zoo.class)
      .readValue(json);

    assertEquals("lacy", zoo.animal.name);
    assertEquals(Zoo.Cat.class, zoo.animal.getClass());
}

6. Jackson 通用注解

接下来让我们讨论 Jackson 的一些更通用的注解。

6.1. @JsonProperty

我们可以添加 @JsonProperty 注解来指示 JSON 中的属性名称

让我们使用 @JsonProperty 来序列化/反序列化属性 name,当我们处理非标准的 getter 和 setter 时

public class MyBean {
    public int id;
    private String name;

    @JsonProperty("name")
    public void setTheName(String name) {
        this.name = name;
    }

    @JsonProperty("name")
    public String getTheName() {
        return name;
    }
}

接下来是我们的测试

@Test
public void whenUsingJsonProperty_thenCorrect()
  throws IOException {
    MyBean bean = new MyBean(1, "My bean");

    String result = new ObjectMapper().writeValueAsString(bean);
    
    assertThat(result, containsString("My bean"));
    assertThat(result, containsString("1"));

    MyBean resultBean = new ObjectMapper()
      .readerFor(MyBean.class)
      .readValue(result);
    assertEquals("My bean", resultBean.getTheName());
}

6.2. @JsonFormat

@JsonFormat 注解指定在序列化日期/时间值时使用的格式。

在下面的示例中,我们使用 @JsonFormat 来控制属性 eventDate 的格式

public class EventWithFormat {
    public String name;

    @JsonFormat(
      shape = JsonFormat.Shape.STRING,
      pattern = "dd-MM-yyyy hh:mm:ss")
    public Date eventDate;
}

然后这是测试

@Test
public void whenSerializingUsingJsonFormat_thenCorrect()
  throws JsonProcessingException, ParseException {
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    EventWithFormat event = new EventWithFormat("party", date);
    
    String result = new ObjectMapper().writeValueAsString(event);
    
    assertThat(result, containsString(toParse));
}

6.3. @JsonUnwrapped

@JsonUnwrapped 定义在序列化/反序列化时应展开/扁平化的值。

让我们看看这到底是如何工作的;我们将使用该注解来展开属性 name

public class UnwrappedUser {
    public int id;

    @JsonUnwrapped
    public Name name;

    public static class Name {
        public String firstName;
        public String lastName;
    }
}

现在让我们序列化此类的一个实例

@Test
public void whenSerializingUsingJsonUnwrapped_thenCorrect()
  throws JsonProcessingException, ParseException {
    UnwrappedUser.Name name = new UnwrappedUser.Name("John", "Doe");
    UnwrappedUser user = new UnwrappedUser(1, name);

    String result = new ObjectMapper().writeValueAsString(user);
    
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("name")));
}

最后,这是输出的样子 – 静态嵌套类的字段与另一个字段一起展开

{
    "id":1,
    "firstName":"John",
    "lastName":"Doe"
}

6.4. @JsonView

@JsonView 指示属性将包含在序列化/反序列化中的视图。

例如,我们将使用 @JsonView 来序列化 Item 实体的一个实例。

首先,让我们从视图开始

public class Views {
    public static class Public {}
    public static class Internal extends Public {}
}

接下来,这是使用视图的 Item 实体

public class Item {
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Internal.class)
    public String ownerName;
}

最后,完整的测试

@Test
public void whenSerializingUsingJsonView_thenCorrect()
  throws JsonProcessingException {
    Item item = new Item(2, "book", "John");

    String result = new ObjectMapper()
      .writerWithView(Views.Public.class)
      .writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("2"));
    assertThat(result, not(containsString("John")));
}

6.5. @JsonManagedReference, @JsonBackReference

@JsonManagedReference@JsonBackReference 注解可以处理父/子关系 并解决循环引用问题。

在下面的示例中,我们使用 @JsonManagedReference@JsonBackReference 来序列化我们的 ItemWithRef 实体

public class ItemWithRef {
    public int id;
    public String itemName;

    @JsonManagedReference
    public UserWithRef owner;
}

我们的 UserWithRef 实体

public class UserWithRef {
    public int id;
    public String name;

    @JsonBackReference
    public List<ItemWithRef> userItems;
}

然后是测试

@Test
public void whenSerializingUsingJacksonReferenceAnnotation_thenCorrect()
  throws JsonProcessingException {
    UserWithRef user = new UserWithRef(1, "John");
    ItemWithRef item = new ItemWithRef(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("userItems")));
}

6.6. @JsonIdentityInfo

@JsonIdentityInfo 指示在序列化/反序列化值时应使用对象身份,例如处理无限递归类型的问题时。

在下面的示例中,我们有一个 ItemWithIdentity 实体,它与 UserWithIdentity 实体之间存在双向关系

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id")
public class ItemWithIdentity {
    public int id;
    public String itemName;
    public UserWithIdentity owner;
}

UserWithIdentity 实体

@JsonIdentityInfo(
  generator = ObjectIdGenerators.PropertyGenerator.class,
  property = "id")
public class UserWithIdentity {
    public int id;
    public String name;
    public List<ItemWithIdentity> userItems;
}

现在 让我们看看如何处理无限递归问题

@Test
public void whenSerializingUsingJsonIdentityInfo_thenCorrect()
  throws JsonProcessingException {
    UserWithIdentity user = new UserWithIdentity(1, "John");
    ItemWithIdentity item = new ItemWithIdentity(2, "book", user);
    user.addItem(item);

    String result = new ObjectMapper().writeValueAsString(item);

    assertThat(result, containsString("book"));
    assertThat(result, containsString("John"));
    assertThat(result, containsString("userItems"));
}

这是序列化后的项和用户的完整输出

{
    "id": 2,
    "itemName": "book",
    "owner": {
        "id": 1,
        "name": "John",
        "userItems": [
            2
        ]
    }
}

6.7. @JsonFilter

@JsonFilter 注解指定在序列化期间使用的过滤器。

首先,我们定义实体并指向过滤器

@JsonFilter("myFilter")
public class BeanWithFilter {
    public int id;
    public String name;
}

现在在完整的测试中,我们定义过滤器,它排除序列化过程中的所有其他属性,只保留 name 属性

@Test
public void whenSerializingUsingJsonFilter_thenCorrect()
  throws JsonProcessingException {
    BeanWithFilter bean = new BeanWithFilter(1, "My bean");

    FilterProvider filters 
      = new SimpleFilterProvider().addFilter(
        "myFilter", 
        SimpleBeanPropertyFilter.filterOutAllExcept("name"));

    String result = new ObjectMapper()
      .writer(filters)
      .writeValueAsString(bean);

    assertThat(result, containsString("My bean"));
    assertThat(result, not(containsString("id")));
}

7. 自定义 Jackson 注解

接下来,让我们看看如何创建自定义 Jackson 注解。 我们可以利用 @JacksonAnnotationsInside 注解:

@Retention(RetentionPolicy.RUNTIME)
    @JacksonAnnotationsInside
    @JsonInclude(Include.NON_NULL)
    @JsonPropertyOrder({ "name", "id", "dateCreated" })
    public @interface CustomAnnotation {}

现在,如果我们对实体使用新的注解

@CustomAnnotation
public class BeanWithCustomAnnotation {
    public int id;
    public String name;
    public Date dateCreated;
}

我们可以看到它将现有的注解组合成一个简单的自定义注解,我们可以将其用作简写

@Test
public void whenSerializingUsingCustomAnnotation_thenCorrect()
  throws JsonProcessingException {
    BeanWithCustomAnnotation bean 
      = new BeanWithCustomAnnotation(1, "My bean", null);

    String result = new ObjectMapper().writeValueAsString(bean);

    assertThat(result, containsString("My bean"));
    assertThat(result, containsString("1"));
    assertThat(result, not(containsString("dateCreated")));
}

序列化过程的输出

{
    "name":"My bean",
    "id":1
}

8. Jackson MixIn 注解

接下来,让我们看看如何使用 Jackson MixIn 注解。

例如,让我们使用 MixIn 注解来忽略 User 类型的属性

public class Item {
    public int id;
    public String itemName;
    public User owner;
}
@JsonIgnoreType
public class MyMixInForIgnoreType {}

然后让我们在实际操作中看看这个效果

@Test
public void whenSerializingUsingMixInAnnotation_thenCorrect() 
  throws JsonProcessingException {
    Item item = new Item(1, "book", null);

    String result = new ObjectMapper().writeValueAsString(item);
    assertThat(result, containsString("owner"));

    ObjectMapper mapper = new ObjectMapper();
    mapper.addMixIn(User.class, MyMixInForIgnoreType.class);

    result = mapper.writeValueAsString(item);
    assertThat(result, not(containsString("owner")));
}

9. 禁用 Jackson 注解

最后,让我们看看如何 禁用所有 Jackson 注解。 我们可以通过禁用 MapperFeature.USE_ANNOTATIONS 来实现,如下例所示

@JsonInclude(Include.NON_NULL)
@JsonPropertyOrder({ "name", "id" })
public class MyBean {
    public int id;
    public String name;
}

现在,在禁用注解之后,它们应该不再起作用,并且库的默认设置应该适用

@Test
public void whenDisablingAllAnnotations_thenAllDisabled()
  throws IOException {
    MyBean bean = new MyBean(1, null);

    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(MapperFeature.USE_ANNOTATIONS);
    String result = mapper.writeValueAsString(bean);
    
    assertThat(result, containsString("1"));
    assertThat(result, containsString("name"));
}

禁用注解之前的序列化结果

{"id":1}

禁用注解之后的序列化结果

{
    "id":1,
    "name":null
}

10. 结论

在本文中,我们研究了 Jackson 注解,只是触及了通过正确使用它们可以获得的灵活性的表面。

支持本文的代码可在 GitHub 上获取。 一旦你Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。
Baeldung Pro – NPI EA (类别 = Baeldung)
announcement - icon

Baeldung Pro 具有完全无广告以及最终具有深色模式,提供干净的学习体验

>> 探索干净的 Baeldung

一旦早期采用者的席位全部用完,价格将上涨并保持在每年 33 美元。

电子书 – HTTP 客户端 – NPI EA (类别=HTTP 客户端)
announcement - icon

Apache HTTP Client 是一个非常强大的库,适用于简单和高级用例,在测试 HTTP 端点时尤其适用。 查看我们的指南,涵盖基本请求和响应处理,以及安全性、Cookie、超时等。

>> 下载电子书

电子书 – Java 并发 – NPI EA (分类=Java 并发)
announcement - icon

在应用程序中处理并发可能是一个棘手的过程,其中包含许多 潜在的陷阱。 扎实的掌握基本知识将有助于最大程度地减少这些问题。

通过我们的 Java 并发 指南开始了解多线程应用程序

>> 下载电子书

电子书 – Java Streams – NPI EA (分类=Java Streams)
announcement - icon

自从 Java 8 引入以来,Stream API 已成为 Java 开发的基础。 基本操作,例如迭代、过滤、映射元素序列,使用起来看似很简单。

但这些也可能被过度使用并陷入一些常见陷阱。

更好地了解 Stream 的工作方式 以及如何将其与其他语言功能结合使用,请查看我们关于 Java Streams 的指南

>> 加入 Pro 并下载电子书

电子书 – 持久化 – NPI EA (分类=持久化)
announcement - icon

您在努力实现正确的持久化层 Spring 吗?

探索电子书

课程 – LS – NPI EA (类别=REST)

announcement - icon

从 Spring Boot 开始,通过 Learn Spring 课程了解核心 Spring。

>> 查看课程

合作伙伴 – Moderne – NPI EA (标签=重构)
announcement - icon

现代 Java 团队行动迅速——但代码库并不总是跟上。 框架会发生变化,依赖关系会漂移,技术债务会累积,直到它开始拖慢交付速度。 OpenRewrite 就是为此而构建的:一个开源重构引擎,可在保持开发人员意图不变的同时自动化重复的代码更改。

由 Moderne 的 OpenRewrite 创建者和维护者领导的每月培训系列,将介绍实际的迁移和现代化模式。 无论您是重构配方的新手,还是准备编写自己的配方,您都将学习以安全且可扩展的方式进行重构的实用方法。

如果您曾经希望重构感觉像编写代码一样自然——并且一样快速——这是一个很好的起点

电子书 Jackson – NPI EA – 3 (类别 = Jackson)
电子书 Jackson – NPI (cat = Jackson)
© .