Jackson注解的高级技巧,你掌握了吗?

内容分享1周前发布
0 4 0

在 Java 开发中,Jackson 是处理 JSON 数据的利器。它通过丰富的注解,让开发者能够轻松地控制 Java 对象与 JSON 之间的转换。无论是在 RESTful API 开发、微服务通信,还是数据持久化与配置解析中,Jackson 都扮演着重大角色。本文将系统地介绍 Jackson 的常用注解,结合示例代码,协助你全面掌握 Jackson 注解体系。

Jackson注解的高级技巧,你掌握了吗?

一、基础序列化控制注解

1. @JsonProperty:指定字段在 JSON 中的名称

public class User {
    public int id;

    @JsonProperty("full_name")
    public String name;
}

序列化结果:{“id”:1,”full_name”:”Alice”}

这个注解可以协助我们自定义字段在 JSON 中的名称,避免直接使用 Java 字段名。

2. @JsonPropertyOrder:控制字段序列化顺序

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

输出:{“name”:”Tom”,”id”:1}

通过这个注解,我们可以指定字段在 JSON 中的顺序,使输出更加符合预期。

3. @JsonInclude:排除空值、默认值等

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
    public String name; // null 时不输出
    public int age;     // 0 会输出(因是基本类型)
}

这个注解可以控制序列化时是否包含某些字段,例如排除空值或默认值,使 JSON 更简洁。

4. @JsonIgnore / @JsonIgnoreProperties:忽略字段或类中的某些属性

@JsonIgnoreProperties({"password"})
public class User {
    public String name;
    public String password; // 不输出
}

或字段级:

public class User {
    public String name;

    @JsonIgnore
    public String password;
}

这两个注解可以用来忽略某些字段,避免敏感信息被序列化。

二、高级序列化控制注解

1. @JsonRawValue:将字段值原样输出为 JSON,而非字符串

public class RawBean {
    public String name;

    @JsonRawValue
    public String metadata; // 值为 "{"role":"admin"}"
}

输出:{“name”:”Bob”,”metadata”:{“role”:”admin”}}

这个注解适用于需要直接嵌入 JSON 字符串的场景,避免被转义。

2. @JsonValue:将整个对象序列化为该方法的返回值(常用于枚举)

public enum Status {
    ACTIVE(1, "active"), INACTIVE(0, "inactive");

    private final int code;
    private final String desc;

    @JsonValue
    public String getDesc() { return desc; }
}

序列化 Status.ACTIVE → “active”

这个注解可以用于枚举类型,将枚举对象直接序列化为某个字段的值。

3. @JsonRootName:为整个对象包裹一个根节点

@JsonRootName("user")
public class User {
    public int id;
    public String name;
}

输出:{“user”:{“id”:1,”name”:”John”}}

这个注解可以在 JSON 的最外层添加一个根节点,方便与某些特定的 JSON 格式对接。

4. @JsonFormat:格式化日期/时间字段

public class Event {
    public String name;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    public Date eventTime;
}

输出:{“name”:”会议”,”eventTime”:”2025-12-08 14:30:00″}

这个注解用于格式化日期和时间字段,确保输出的日期格式符合需求。

5. @JsonNaming:统一字段命名策略(如蛇形命名)

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class NamingBean {
    private String userName;
}

输出:{“user_name”:”Alice”}

这个注解可以统一类中所有字段的命名策略,例如将驼峰命名转换为蛇形命名,方便与某些特定的 JSON 格式对接。

三、反序列化控制注解

1. @JsonCreator:通过构造器或静态工厂方法反序列化

public class User {
    public final String name;
    public final int id;

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

这个注解可以指定通过构造器或静态工厂方法来反序列化对象,适用于不可变对象的创建。

2. @JsonAlias:支持多个 JSON 字段名映射到同一 Java 字段

public class Person {
    @JsonAlias({"fName", "first_name"})
    public String firstName;
}

支持 {“fName”:”Tom”} 或 {“first_name”:”Tom”}

这个注解可以为字段指定多个别名,使 JSON 数据在字段名不一致时也能正确反序列化。

3. @JsonAnySetter:将 JSON 中未知字段存入 Map

public class ExtendableBean {
    public String name;
    private Map<String, String> props = new HashMap<>();

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

这个注解可以将 JSON 中未知的字段存储到一个 Map 中,方便处理动态字段。

4. @JacksonInject:从外部注入值,而非从 JSON 读取

public class InjectBean {
    @JacksonInject("tenantId")
    public String tenantId;
    public String name;
}

// 使用
InjectableValues values = new InjectableValues.Std().addValue("tenantId", "T1001");
User u = mapper.reader(values).forType(InjectBean.class).readValue(json);

这个注解可以将外部值注入到对象中,而不是从 JSON 数据中读取,适用于某些需要外部配置的场景。

5. @JsonDeserialize:指定自定义反序列化器(与 @JsonSerialize 对应)

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

这个注解可以指定一个自定义的反序列化器,用于处理复杂的字段类型。

四、多态类型处理

1. @JsonTypeInfo + @JsonSubTypes:支持多态反序列化

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

public class Dog extends Animal {
    public double barkVolume;
}

输入:{“type”:”dog”,”name”:”Lacy”,”barkVolume”:8.5} → 自动创建 Dog 实例

这两个注解可以用于处理多态类型,使 Jackson 能够根据 JSON 中的类型标识正确地反序列化为对应的子类。

2. @JsonTypeName:为子类指定逻辑类型名

@JsonTypeName("canine")
public class Dog extends Animal { ... }

这个注解可以为子类指定一个逻辑类型名,方便在多态反序列化时识别。

3. @JsonTypeId:将字段值作为类型 ID 使用

public class TypeIdBean {
    public int id;

    @JsonTypeId
    public String typeName;
}

序列化结果:[“Admin”, {“id”:1}]

这个注解可以将某个字段的值用作类型 ID,用于多态类型标识。

4. @JsonTypeIdResolver:自定义类型 ID 与 Java 类的映射逻辑

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type")
@JsonTypeIdResolver(MyIdResolver.class)
public abstract class Animal { ... }

配合 TypeIdResolverBase 实现灵活映射(如 bean1 → FirstBean.class)。

这个注解可以自定义类型 ID 与 Java 类的映射逻辑,适用于复杂的多态类型场景。

五、循环引用与对象标识

1. @JsonIdentityInfo:通过对象 ID 解决循环引用

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class User {
    public int id;
    public List<Order> orders;
}

public class Order {
    public int id;
    public User user; // 第二次出现时只输出 id
}

这个注解可以为对象生成一个唯一的 ID,并在序列化时使用这个 ID 来解决循环引用问题。

2. @JsonIdentityReference(alwaysAsId = true):强制始终输出 ID,而非完整对象

@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator.class)
@JsonIdentityReference(alwaysAsId = true)
public class UserRef {
    public int id;
    public String name;
}

序列化结果:”1″(直接输出 ID 字符串)

这个注解可以强制始终输出对象的 ID,而不是完整的对象,适用于某些需要简化输出的场景。

3. @JsonManagedReference / @JsonBackReference:用于父子双向关联(如订单-用户)

public class Order {
    public int id;

    @JsonManagedReference
    public User user;
}

public class User {
    public String name;

    @JsonBackReference
    public List<Order> orders;
}

这个注解组合可以用于处理父子双向关联,避免在序列化时出现循环引用。

六、动态控制与扩展:高级场景下的灵活映射

Jackson 不仅支持静态注解控制 JSON 行为,还提供了一系列动态、运行时可控的机制,用于应对权限隔离、字段裁剪、虚拟属性注入、构建器反序列化等复杂工程需求。本节将详细介绍 @JsonView、@JsonFilter、@JsonAppend、@JsonPOJOBuilder 和 @JsonPropertyDescription 的完整用法与适用场景。

1. @JsonView:基于视图的字段可见性控制

核心作用

@JsonView 允许你定义多个“视图”,并在序列化/反序列化时按需激活某一视图,仅处理该视图所包含的字段。这是实现“同一实体类、多种输出形态”的优雅方案。

示例代码

// 定义视图层级
class Views {
    public static class Public {}                 // 基础视图
    public static class Internal extends Public {} // 内部视图,包含 Public 所有字段
}

// 在实体类中标注字段
public class User {
    @JsonView(Views.Public.class)
    public Long id;

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

    @JsonView(Views.Internal.class)
    public String email;

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

序列化时,根据不同的视图需求,可以输出不同的字段组合:

// 公开视图:仅返回基础信息
String publicJson = mapper
    .writerWithView(Views.Public.class)
    .writeValueAsString(user);
// 输出:{"id":1001,"username":"alice"}

// 内部视图:返回完整信息
String internalJson = mapper
    .writerWithView(Views.Internal.class)
    .writeValueAsString(user);
// 输出:{"id":1001,"username":"alice","email":"alice@company.com","phoneNumber":"138****5678"}

适用场景

  • 对外 API vs 内部管理后台
  • 用户基本信息 vs 敏感信息(如手机号、邮箱)
  • 减少移动端流量(仅返回必要字段)

2. @JsonFilter:运行时动态字段过滤

核心作用

相比 @JsonView 的“编译期静态定义”,@JsonFilter 支持在运行时动态决定保留或排除哪些字段,灵活性更高。

示例代码

@JsonFilter("userFilter") // 绑定过滤器名称
public class User {
    public String name;
    public String email;
    public String password;
    public String department;
}
// 白名单:仅保留 name 和 email(适用于公开接口)
SimpleBeanPropertyFilter allowList = SimpleBeanPropertyFilter
    .filterOutAllExcept("name", "email");

// 黑名单:排除 password(适用于内部接口脱敏)
SimpleBeanPropertyFilter denyList = SimpleBeanPropertyFilter
    .serializeAllExcept("password");

FilterProvider filters = new SimpleFilterProvider()
    .addFilter("userFilter", allowList); // 或 denyList

String json = mapper.writer(filters).writeValueAsString(user);

适用场景

  • 动态数据脱敏:根据用户角色决定是否返回手机号、身份证号等
  • 多租户系统:不同租户可见字段不同
  • API 版本兼容:v1 返回 a,b;v2 返回 a,b,c

3. @JsonAppend:无侵入式注入虚拟属性

核心作用

在不修改原始类的前提下,向 JSON 输出中注入额外的“虚拟字段”,如 version、traceId、apiSource 等元数据。

示例代码

@JsonAppend(attrs = {
    @JsonAppend.Attr(value = "version", type = String.class),
    @JsonAppend.Attr(value = "traceId", type = String.class),
    @JsonAppend.Attr(value = "timestamp", type = Long.class)
})
public class Product {
    public String id;
    public String name;
    public double price;
}
Product p = new Product();
p.id = "P1001";
p.name = "智能手表";
p.price = 1999.0;

String json = mapper.writer()
    .withAttribute("version", "v2.1")
    .withAttribute("traceId", UUID.randomUUID().toString())
    .withAttribute("timestamp", System.currentTimeMillis())
    .writeValueAsString(p);

输出结果:

{
  "id": "P1001",
  "name": "智能手表",
  "price": 1999.0,
  "version": "v2.1",
  "traceId": "a1b2c3d4-...",
  "timestamp": 1700000000000
}

适用场景

  • API 版本标识(便于前端兼容)
  • 链路追踪(注入 traceId)
  • 审计字段(如 lastModifiedBy)
  • 多端标识(如 source: “mobile”)

4. @JsonPOJOBuilder:支持非标准 Builder 模式的反序列化

核心作用

当使用 Builder 模式构造不可变对象时,若 Builder 方法不符合 Jackson 默认命名规则(如 withName 或 setName),需用此注解显式配置。

示例代码

// 主类:指定使用 Builder 反序列化
@JsonDeserialize(builder = User.Builder.class)
public class User {
    private final String name;
    private final int age;

    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static class Builder {
        private String nameValue;
        private int ageValue;

        // 非标准前缀:constructXxx
        public Builder constructName(String name) {
            this.nameValue = name;
            return this;
        }

        public Builder constructAge(int age) {
            this.ageValue = age;
            return this;
        }

        public User build() {
            return new User(nameValue, ageValue);
        }
    }
}
// 配置 Builder 的方法命名规则
@JsonPOJOBuilder(
    withPrefix = "construct",     // setter 方法前缀
    buildMethodName = "build"     // 构建方法名
)
public static class Builder { /* 同上 */ }
// 反序列化
String json = "{"name":"Tom", "age":30}";
User user = new ObjectMapper().readValue(json, User.class);
// 成功创建 User 对象

适用场景

  • 不可变对象(Immutable POJO)
  • 领域驱动设计(DDD)中的聚合根
  • 与 Record Builder 结合

5. @JsonPropertyDescription:为 JSON Schema 生成人类可读描述

核心作用

在生成 JSON Schema(如用于 OpenAPI/Swagger、JSON 校验)时,为字段添加语义化描述,提升接口文档可读性。

示例代码

public class User {
    @JsonPropertyDescription("用户全局唯一标识,由系统分配")
    public String id;

    @JsonPropertyDescription("登录用户名,3-20位字母或数字")
    public String username;

    @JsonPropertyDescription("用户电子邮箱,用于接收通知")
    public String email;
}

生成的 JSON Schema 片段:

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "用户全局唯一标识,由系统分配"
    },
    "username": {
      "type": "string",
      "description": "登录用户名,3-20位字母或数字"
    },
    "email": {
      "type": "string",
      "description": "用户电子邮箱,用于接收通知"
    }
  }
}

适用场景

  • 自动生成接口文档
  • 前端/后端契约校验
  • 数据治理与元数据管理

七、其他高级特性

1. 自定义组合注解

通过 @JacksonAnnotationsInside 定义复用注解。

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

@PublicAPI
public class User { ... }

2. MixIn 注解

不修改原类,动态添加注解行为。

// 原始类无法修改
public class ThirdPartyUser { public String password; }

// MixIn 类
abstract class UserMixIn {
    @JsonIgnore
    public String password;
}

// 注册
mapper.addMixIn(ThirdPartyUser.class, UserMixIn.class);

3. 禁用所有注解

全局关闭注解功能(调试或安全场景)。

ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_ANNOTATIONS);

八、总结:Jackson 注解全景图

功能类别

核心注解

字段映射

@JsonProperty, @JsonGetter, @JsonSetter

命名与顺序

@JsonNaming, @JsonPropertyOrder

日期格式

@JsonFormat

空值/默认值控制

@JsonInclude, @JsonIncludeProperties

忽略字段

@JsonIgnore, @JsonIgnoreProperties, @JsonIgnoreType

循环引用

@JsonIdentityInfo, @JsonIdentityReference, @JsonManagedReference

多态类型

@JsonTypeInfo, @JsonSubTypes, @JsonTypeName, @JsonTypeId, @JsonTypeIdResolver

动态控制

@JsonView, @JsonFilter

扩展与注入

@JsonAppend, @JsonAnyGetter, @JsonAnySetter, @JacksonInject

构建器支持

@JsonCreator, @JsonPOJOBuilder

自定义序列化

@JsonSerialize, @JsonDeserialize, @JsonValue

工具与元信息

@JsonPropertyDescription, @JsonRootName, @JsonAutoDetect

Jackson 注解体系强劲而灵活,几乎可以应对所有 JSON 处理场景。合理组合使用这些注解,可大幅提升系统健壮性、安全性与可维护性。提议在项目中:

  • 全局统一命名策略、日期格式
  • 用 @JsonView 或 @JsonFilter 实现权限控制
  • 用 @JsonIdentityInfo 安全处理循环引用
  • 用 @JsonAppend 无侵入注入元数据

Jackson注解的高级技巧,你掌握了吗?

致谢

感谢您阅读到这里!如果您觉得这篇文章对您有所协助或启发,希望您能给我一个小小的鼓励:

  • 点赞:您的点赞是我继续创作的动力,让我知道这篇文章对您有价值!
  • 关注:关注我,您将获得更多精彩内容和最新更新,让我们一起探索更多知识!
  • 收藏:方便您日后回顾,也可以随时找到这篇文章,再次阅读或参考。
  • 转发:如果您认为这篇文章对您的朋友或同行也有协助,欢迎转发分享,让更多人受益!

您的每一个支持都是我不断进步的动力,超级感谢您的陪伴和支持!如果您有任何疑问或想法,也欢迎在评论区留言,我们一起交流!

© 版权声明

相关文章

4 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    每日新看点 读者

    廉颇老矣

    无记录
  • 头像
    林彧 读者

    这原文是,凭谁问,廉颇老矣,尚能饭否。我想说的是,如果是别人问,那就是借坡下驴,说吃不下了。但是自己问自己,那就应该答还能吃下一头驴。明心见性,知行合一 与君共勉

    无记录
  • 头像
    吴克群 读者

    收藏了,感谢分享

    无记录
  • 头像
    清灵 读者

    感谢[祝福][祝福][祝福]

    无记录