💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、豆包、星火、月之暗面及文生图、文生视频 广告
在 Java DTO 里把 `@JsonFormat` + `@JsonSerialize` + `@JsonDeserialize` 三个注解同时写在同一个 `LocalDate` 字段上,**绝大多数情况下是冗余的**,甚至可能因为配置不一致而互相覆盖,导致序列化/反序列化行为不符合预期。下面把原理、最佳实践和常见坑一次讲清。 ------------------------------------------------ 1. 每个注解到底干了什么 ------------------------------------------------ | 注解 | 归属 | 作用时机 | 典型用途 | |---|---|---|---| | `@JsonFormat` | Jackson 内核 | 序列化 & 反序列化 | 指定“格式字符串”和时区,只对外层是 **时间/日期类型** 的字段生效。 | | `@JsonSerialize` | Jackson 内核 | 仅序列化 | 完全自定义序列化逻辑,会 **覆盖** `@JsonFormat` 的序列化行为。 | | `@JsonDeserialize` | Jackson 内核 | 仅反序列化 | 完全自定义反序列化逻辑,会 **覆盖** `@JsonFormat` 的反序列化行为。 | ------------------------------------------------ 2. 一起写会发生什么 ------------------------------------------------ - 如果只写 `@JsonFormat(pattern="yyyy-MM-dd", timezone="GMT+8")`, Jackson 自带的 `LocalDate` 内置 (de)serializer 会识别该注解,按指定格式和时区工作,**不需要**额外写 `using = ...`。 - 一旦你又写了 `@JsonSerialize(using = LocalDateSerializer.class)` `@JsonDeserialize(using = LocalDateDeserializer.class)` 就等于告诉 Jackson:“别用内置逻辑,改用我自己指定的 Serializer/Deserializer”。 而 `LocalDateSerializer` / `LocalDateDeserializer` 的源码里 **并没有去读 `@JsonFormat` 的 pattern 和 timezone**,它们只会走 JDK 默认的 `toString` / `parse` 逻辑(ISO 格式 `yyyy-MM-dd`)。 结果: – 序列化:永远是 `yyyy-MM-dd`,**时区被忽略**(`LocalDate` 本身不带时区,所以 timezone 参数其实没意义)。 – 反序列化:只能接受 ISO 格式,**pattern 参数被忽略**。 ------------------------------------------------ 3. 结论:怎么写最干净 ------------------------------------------------ 1. **99% 场景**——只想固定格式、时区,**不要写后两个注解**: ```java public class OrderDTO { @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private LocalDate orderDate; } ``` 2. **1% 场景**——格式/逻辑太特殊,内置做不到,才自己写 Serializer/Deserializer,并且**不要再写 `@JsonFormat`**,避免歧义: ```java public class OrderDTO { @JsonSerialize(using = MyCustomLocalDateSerializer.class) @JsonDeserialize(using = MyCustomLocalDateDeserializer.class) private LocalDate orderDate; } ``` ------------------------------------------------ 4. 常见疑问 ------------------------------------------------ Q1. 时区对 `LocalDate` 有用吗? → 没用。`LocalDate` 只有年月日,没有时分区,因此 `@JsonFormat(timezone = "...")` 对 `LocalDate` 字段**实际不起作用**;不过写上也不会报错,只是给人看。 Q2. 我想统一全局格式,不想每个字段写 `@JsonFormat`? → 在 `application.yml`(Spring Boot)里配: ```yaml spring: jackson: serialization: write-dates-as-timestamps: false deserialization: accept-single-value-as-array: true date-format: yyyy-MM-dd # 对 Date 类型生效 ``` 或者注册一个 `JavaTimeModule` 并自定义 `LocalDate` 的 (de)serializer,一次性全局生效。 ------------------------------------------------ 一句话总结 **“要么只写 `@JsonFormat`,要么只写 `@JsonSerialize`/`@JsonDeserialize`,不要三者一起堆在 `LocalDate` 上。”**