ObjectMapper 是 Jackson 库里的核心类,全称在包 com.fasterxml.jackson.databind.ObjectMapper。
可以把它理解成一台 序列化 / 反序列化引擎:
| 方向 | 含义 | 典型方法 |
|---|---|---|
| 序列化(Serialize) | Java 对象 → JSON 字符串/字节 | writeValueAsString(obj) |
| 反序列化(Deserialize) | JSON 字符串/字节 → Java 对象 | readValue(json, Class) |
在 memo-api 的 pom.xml 里已经引入了 Jackson:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
Spring Boot 默认用 Jackson 处理 HTTP 请求体、响应体(@RequestBody、@RestController 返回 JSON),所以项目里 几乎一定已经有一个配置好的 ObjectMapper Bean,可以直接 @Autowired 注入使用。
没有 ObjectMapper 时,你要自己拼 JSON:
// 痛苦且易错
String json = "{\"id\":1,\"nameCn\":\"中国\",\"nameEn\":\"China\"}";
有了 ObjectMapper:
WeatherCountryOptionVo vo = new WeatherCountryOptionVo();
vo.setId(1L);
vo.setNameCn("中国");
String json = objectMapper.writeValueAsString(vo);
// {"id":1,"nameCn":"中国","nameEn":null,...}
反过来,从 JSON 文件读回对象也一样简单。这就是它在工程里无处不在的原因。
WeatherPlaceServiceImpl 里的文件缓存逻辑,就是一个典型场景:
private <T> R loadWithCache(WeatherCacheType type, long dataId,
TypeReference<List<T>> typeRef,
Supplier<List<T>> dbLoader) {
try {
long version = resolveDataVersion();
Optional<String> cached = weatherFileCacheUtil.read(version, type, dataId);
if (cached.isPresent()) {
// 反序列化:JSON 字符串 → List<Vo>
List<T> list = objectMapper.readValue(cached.get(), typeRef);
return R.SUCCESS(successMsg(type)).data(list);
}
List<T> list = dbLoader.get();
// 序列化:List<Vo> → JSON 字符串
String json = objectMapper.writeValueAsString(list);
weatherFileCacheUtil.write(version, type, dataId, json);
return R.SUCCESS(successMsg(type)).data(list);
} catch (Exception e) {
log.error("查询天气地点失败: type={}, dataId={}", type.getCode(), dataId, e);
return R.FAILED("获取地点列表失败.");
}
}
流程可以画成:
查库得到 List<WeatherCountryOptionVo>
│
▼ writeValueAsString
JSON 字符串写入磁盘
│
▼ 下次请求 readValue
List<WeatherCountryOptionVo> 直接返回
这里 没有走 HTTP,但同样需要「对象 ↔ JSON」转换,所以用 ObjectMapper 很合适。
String json = objectMapper.writeValueAsString(user);
User user = objectMapper.readValue(json, User.class);
泛型在运行时会被擦掉,下面 不能 这样写:
// 编译能过,运行会报错或类型不对
List<WeatherCountryOptionVo> list =
objectMapper.readValue(json, List.class); // 实际得到 List<LinkedHashMap>
正确写法是用 TypeReference:
List<WeatherCountryOptionVo> list = objectMapper.readValue(
json,
new TypeReference<List<WeatherCountryOptionVo>>() {}
);
TypeReference 通过匿名子类,在运行时保留泛型信息,让 Jackson 知道要反序列化成「List<WeatherCountryOptionVo>」,而不是一堆 Map。
objectMapper.writeValue(new File("cache.json"), list);
List<WeatherCountryOptionVo> list = objectMapper.readValue(
new File("cache.json"),
new TypeReference<List<WeatherCountryOptionVo>>() {}
);
Map<String, Object> map = objectMapper.convertValue(
vo, new TypeReference<Map<String, Object>>() {});
WeatherCountryOptionVo vo2 = objectMapper.convertValue(map, WeatherCountryOptionVo.class);
JsonNode node = objectMapper.readTree(json);
String name = node.get("nameCn").asText();
写 Controller 时通常 不用手动调 ObjectMapper:
@GetMapping("/user")
public User getUser() {
return user; // Spring 自动用 ObjectMapper 转成 JSON 响应
}
@PostMapping("/user")
public R save(@RequestBody User user) { // 请求体 JSON 自动转成 User
...
}
底层就是同一个(或同一套配置下的)ObjectMapper。
手动注入的场景:缓存、消息队列、读写文件、调用第三方 API、日志打点等 Spring MVC 管不到的地方——天气文件缓存就属于这一类。
| 场景 | 说明 |
|---|---|
| REST API | 请求/响应 JSON(Spring 自动完成) |
| 文件缓存 | 对象写入磁盘,读回对象(天气地点缓存) |
| Redis 存对象 | 常存 JSON 字符串(若不用 JDK 序列化) |
| MQ 消息体 | Kafka/RabbitMQ 消息序列化 |
| 第三方 API | 解析微信/支付/天气接口返回的 JSON |
| 配置中心 | 把 JSON 配置转成 Java 配置类 |
| 日志/审计 | 把对象打成 JSON 便于检索 |
| 深拷贝 | readValue(writeValueAsString(obj), X.class) 做简单拷贝(有性能代价) |
Jackson 默认按 getter/setter 或字段名 与 JSON 键匹配:
public class WeatherCountryOptionVo {
private Long id;
private String nameCn; // JSON 键默认也是 nameCn
}
常用注解:
@JsonProperty("name_cn") // 指定 JSON 字段名
private String nameCn;
@JsonIgnore // 序列化时忽略
private String password;
@JsonInclude(JsonInclude.Include.NON_NULL) // null 不输出
public class XxxVo { ... }
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
数据库列名是 name_cn、Java 是 nameCn 时,要么实体用 @TableField("name_cn")(MyBatis),JSON 侧用 @JsonProperty,要么统一命名策略。
推荐:注入容器里的 Bean(与 MVC 配置一致)
@Autowired
private ObjectMapper objectMapper;
不推荐:每次 new
ObjectMapper mapper = new ObjectMapper(); // 缺少 Spring 全局配置
若需自定义(日期格式、忽略未知字段等),应通过 @Bean 或 Jackson2ObjectMapperBuilderCustomizer 统一配置,而不是业务类里各自 new。
常见全局配置示例:
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
return mapper;
}
FAIL_ON_UNKNOWN_PROPERTIES = false:JSON 多了字段不报错(对接外部 API 时常用)NON_NULL:响应更干净,体积更小也可以在 application.yaml 中配置:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
default-property-inclusion: non_null
deserialization:
fail-on-unknown-properties: false
| 库 | 特点 |
|---|---|
| Jackson | Spring 默认,性能好,生态大 |
| Gson | Google 出品,API 简单,老项目常见 |
| Fastjson / Fastjson2 | 阿里系,国内用得多,注意安全版本 |
| 手动拼接 | 仅极简场景,维护成本高 |
前面已说,List.class 会丢泛型。
Date / LocalDateTime 序列化格式要和前端约定一致,否则解析失败。
A 引用 B,B 又引用 A,可能 StackOverflowError。可用 @JsonIgnore 或 @JsonManagedReference / @JsonBackReference。
每次请求都 writeValueAsString 大列表会有 CPU 开销;文件缓存策略是 写一次、多次读,这是正确方向。
readValue / writeValueAsString 会抛 JsonProcessingException(检查异常),需要 try-catch 或方法签名 throws。
ObjectMapper
│
┌───────────────┼───────────────┐
▼ ▼ ▼
HTTP 响应 文件缓存 MQ / Redis
(Spring 自动) (WeatherPlace 手动) (业务手动)
│ │ │
Java 对象 ←──── JSON 字符串 ────→ Java 对象
一句话记:ObjectMapper = Java 对象和 JSON 之间的标准转换器;Web 层 Spring 帮你调,缓存/文件/消息等非 Web 场景你自己 @Autowired 来调。
spring.jackson.* 配置项Java 里做 JSON 序列化,常见三大库:Jackson、Gson、Fastjson(2)。它们解决的是同一类问题,但设计取向、生态和适用场景不同。
| 库 | 一句话 |
|---|---|
| Jackson | Spring 生态默认,功能全、扩展强,企业项目首选 |
| Gson | Google 出品,API 极简,适合轻量、Android、小工具 |
| Fastjson2 | 阿里系,国内文档多、某些场景性能突出,需关注版本与安全 |
说明:老版 Fastjson 1.x 曾有多起安全漏洞,新项目应使用 Fastjson2(
com.alibaba.fastjson2),不要再用 1.x。
假设有如下 Java 类:
public class User {
private Long id;
private String name;
// getter / setter ...
}
JSON:{"id":1,"name":"张三"}
ObjectMapper mapper = new ObjectMapper();
// 序列化
String json = mapper.writeValueAsString(user);
// 反序列化
User u = mapper.readValue(json, User.class);
// 泛型集合
List<User> list = mapper.readValue(json,
new TypeReference<List<User>>() {});
Gson gson = new Gson();
// 序列化
String json = gson.toJson(user);
// 反序列化
User u = gson.fromJson(json, User.class);
// 泛型集合(TypeToken,和 TypeReference 类似)
List<User> list = gson.fromJson(json,
new TypeToken<List<User>>() {}.getType());
// 序列化
String json = JSON.toJSONString(user);
// 反序列化
User u = JSON.parseObject(json, User.class);
// 泛型集合
List<User> list = JSON.parseObject(json,
new TypeReference<List<User>>() {});
| 对比项 | Jackson | Gson | Fastjson2 |
|---|---|---|---|
| 入口类 | ObjectMapper |
Gson |
JSON 静态方法 |
| API 风格 | 偏「引擎/配置」 | 极简 | 静态方法 + 链式 |
| 泛型集合 | TypeReference |
TypeToken |
TypeReference |
| Spring Boot 默认 | ✅ 是 | ❌ 否 | ❌ 否 |
| 维度 | Jackson | Gson | Fastjson2 |
|---|---|---|---|
| 性能 | 优秀,大规模生产验证 | 中等,够用 | 优秀,部分基准很快 |
| 功能丰富度 | 很强(树模型、流、多数据格式) | 中等 | 强(注解多、特性多) |
| 学习成本 | 中等 | 低 | 中等 |
| 注解支持 | @JsonProperty 等 |
@SerializedName 等 |
@JSONField 等 |
| 未知字段 | 可配置忽略/失败 | 默认忽略 | 可配置 |
| 日期处理 | 需配置,与 Java 8 时间配合好 | 需自定义 | 注解/全局配置较方便 |
| 流式/大 JSON | ✅JsonParser 流式解析 |
较弱 | 支持 |
| 多数据格式 | XML/YAML/CBOR 等(模块扩展) | 仅 JSON | 主要 JSON |
| Spring 集成 | 原生、零配置 | 需手动替换 | 需手动替换 |
| Android | 可用但包略大 | 很常见 | 可用 |
| 安全历史 | 相对稳定 | 相对稳定 | 1.x 有漏洞史,2.x 需跟进版本 |
| 维护方 | FasterXML 社区 | 阿里巴巴 |
// Jackson
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// Gson
Gson gson = new GsonBuilder().serializeNulls().create(); // 默认就不输出 null
// Fastjson2
JSON.toJSONString(user, JSONWriter.Feature.WriteNulls); // 默认一般不输出 null
// Jackson
@JsonProperty("user_name")
private String userName;
// Gson
@SerializedName("user_name")
private String userName;
// Fastjson2
@JSONField(name = "user_name")
private String userName;
// Jackson
@JsonIgnore
private String password;
// Gson
@Expose(serialize = false) // 需 GsonBuilder.excludeFieldsWithoutExposeAnnotation()
private String password;
// Fastjson2
@JSONField(serialize = false)
private String password;
混用多个库时,同一实体上的注解不通用,换库往往要改注解或写适配层。
网上常见结论(仅供参考,以你实际压测为准):
new ObjectMapper/Gson、是否用流式解析、是否输出无用字段、磁盘/网络 IO。实践建议:不要为了「快 5%」换库;优先看团队熟悉度、Spring 集成、安全与维护。
适合:
RedisTemplate、Feign 等同一套序列化配置你们 AiHub / memo-api 属于这一类:天气地点文件缓存、@RequestBody、OSS SDK 依赖的 Jackson 版本,继续用 ObjectMapper 最省事。
适合:
不太适合:
HttpMessageConverter,成本高)适合:
@JSONField、中文文档、特定特性有依赖需要注意:
仅适合:字段极少、固定模板、性能极端且结构不变。业务对象一多就维护灾难,不推荐。
┌─────────────────────────────────────────────────────────┐
│ 推荐:一个进程内,HTTP 层只认一种 JSON 库(Jackson) │
│ 非 HTTP(文件缓存、MQ)可注入同一个 ObjectMapper │
└─────────────────────────────────────────────────────────┘
| 做法 | 评价 |
|---|---|
Spring MVC 用 Jackson,文件缓存也用同一个ObjectMapper |
✅ 推荐 |
Controller 用 Jackson,某工具类单独new Gson() |
⚠️ 能跑,注意行为差异(日期、null、字段名) |
同一实体既写@JsonProperty 又写 @JSONField |
❌ 冗余,换库时易漏改 |
| 为了性能同时引入 Jackson + Fastjson2 | ❌ 依赖膨胀,收益通常不值 |
从 Gson / Fastjson 迁到 Jackson(接入 Spring Boot 时常见):
toJson / fromJson 为 writeValueAsString / readValueTypeToken → TypeReference@JsonProperty、@JsonIgnore 等| 如果你… | 建议 |
|---|---|
| 做 Spring Boot 后端 | Jackson |
| 做 Android App | Gson 或 Moshi(Kotlin 更常见) |
| 维护老项目且已大量 Fastjson 1.x | 计划升级到Fastjson2,别长期停在 1.x |
| 写天气缓存、读写本地 JSON 文件 | Jackson(与接口层一致) |
| 写一个 50 行的小工具 | Gson 或 Jackson 都行 |
| 对接第三方返回的「非标准 JSON」 | JacksonJsonNode 或 JsonPath,灵活 |
回到 ObjectMapper:
你当前 WeatherPlaceServiceImpl 用 @Autowired ObjectMapper 写文件缓存,与 Spring 返回 JSON 使用同一套规则,这是合理架构;若此处改成 new Gson(),同一份 WeatherCountryOptionVo 序列化结果可能与接口响应在日期、null 字段上不一致,前端/缓存对账会变麻烦。
<!-- Jackson(Spring Boot 已自带,一般无需重复声明) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>
<!-- Fastjson2(勿用 1.x 的 com.alibaba:fastjson) -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
</dependency>
版本号请以 Maven Central 当前稳定版为准。




