第一次用Jackson处理JSON数据时,我踩过一个典型的坑:前端传过来的日期字符串"2023-05-20"死活解析不了。调试半天才发现,默认配置下Jackson期望的是时间戳格式。这个经历让我意识到,掌握SerializationFeature和DeserializationFeature这两个配置枚举类,就像拿到了处理JSON数据的万能钥匙。
在实际项目中,JSON数据格式千变万化。你可能需要:
这些场景都需要通过配置ObjectMapper的特性来实现。以日期问题为例,只需要这样配置:
java复制ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
上周对接第三方API时,他们返回的JSON里突然多出几个未定义的字段,导致我们的服务直接崩溃。后来发现只需要一个配置就能解决:
java复制mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
这些特性特别实用:
有些场景需要严格校验:
java复制// 确保必填字段不为null
mapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
// 防止枚举值被数字伪造
mapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
特别提醒:FAIL_ON_READING_DUP_TREE_KEY能防止JSON注入攻击,当出现重复键时会抛出异常。这在处理用户输入的JSON时特别重要。
最近做API开发时,前端同事总抱怨JSON难以阅读。其实用这几个特性就能改善:
java复制mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(SerializationFeature.SORT_PROPERTIES_ALPHABETICALLY);
输出效果对比:
json复制// 美化前
{"name":"张三","age":25,"address":{"city":"北京","street":"中关村"}}
// 美化后
{
"address" : {
"city" : "北京",
"street" : "中关村"
},
"age" : 25,
"name" : "张三"
}
处理财务数据时,BigDecimal的科学计数法会让对账人员抓狂:
java复制mapper.enable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN);
枚举序列化也有讲究:
java复制// 默认用name()值
public enum Status { RUNNING, STOPPED }
// 使用toString()值
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
// 甚至可以用索引值
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_INDEX);
经过多个项目验证,这套配置适合大多数Web服务:
java复制ObjectMapper mapper = new ObjectMapper()
// 反序列化配置
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
// 序列化配置
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS)
// 日期格式
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
对金融等需要严格校验的场景:
java复制ObjectMapper strictMapper = new ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
.enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES)
.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
在高并发场景下,可以这样调优:
java复制// 关闭非必要特性提升性能
ObjectMapper fastMapper = new ObjectMapper()
.disable(SerializationFeature.INDENT_OUTPUT)
.disable(SerializationFeature.SORT_PROPERTIES_ALPHABETICALLY)
.disable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
第一次用WRAP_ROOT_VALUE特性时,我花了三小时才搞明白为什么所有JSON都多了一层包装。后来发现需要配合@JsonRootName注解使用:
java复制mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
@JsonRootName("user")
public class User {
private String name;
// getters/setters
}
// 输出结果:{"user":{"name":"张三"}}
另一个容易忽略的点是WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS和WRITE_CHAR_ARRAYS_AS_JSON_STRINGS的互斥性。同时开启时,后者会覆盖前者。
在处理日期时,WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS和WRITE_DATES_AS_TIMESTAMPS的组合也容易让人困惑。建议在微服务项目中统一约定日期格式,而不是依赖默认的时间戳。
最后提醒:Jackson的配置是全局生效的。如果在Spring项目中使用,通过@Bean配置的ObjectMapper会影响所有JSON处理逻辑。对于需要特殊处理的场景,可以考虑创建新的ObjectMapper实例。