Map序列化为JSON需预处理非基础类型:时间转ISO-8601或long,枚举用code、自定义对象拆解为子Map,统一配置ObjectMapper定制序列化器,并规避key非String、循环引用和数组类型混用问题。
map 序列化为 json 字符串时,遇到日期、枚举、自定义对象等非基础类型,直接调用 tojson() 往往会输出不规范的字符串(如 "wed may 18 11:01:00 cst 2026")或抛出异常。关键不是“能不能转”,而是“怎么转得可控、可读、可复用”。核心在于**提前标准化值,再统一编码**,而非依赖库的默认行为。
统一预处理日期与时间字段
Java 中 java.util.Date、LocalDateTime、Instant 等类型不能直接进 Map 后再交给 Jackson/Gson 处理——不同库默认格式不一致,且容易忽略时区。正确做法是:在 put 进 Map 前,就完成格式化或转换。
- 将所有时间字段统一转为 ISO-8601 字符串(含时区),例如:
"2026-05-18T11:01:00.123+08:00",使用DateTimeFormatter.ISO_OFFSET_DATE_TIME - 若需时间戳(毫秒),统一转为
long类型,避免 String 和 Number 混用 - 对 Map 中已存在的 Date 类型值,可用递归遍历 + 类型判断做就地替换:
if (value instanceof Date) map.put(key, formatter.format((Date)value))
枚举与自定义对象转为语义化字符串
枚举直接进 Map 会被序列化为 name()(如 "ACTIVE"),但前端可能需要中文描述或 code 字段。自定义对象(如 UserStatus)更不能裸放——JSON 不认识 Java 类。
- 枚举建议实现接口(如
Descriptive),暴露getCode()和getLabel(),put 时显式调用:map.put("status", userStatus.getCode()) - 自定义对象不要整个塞进 Map;提取关键字段组装子 Map:
Map.of("id", u.getId(), "name", u.getName()),或提前转成标准 DTO Map - 若必须保留对象结构,确保该类有 public 无参构造 + getter,且被 Jackson 注解(如
@JsonInclude(NON_NULL))约束输出
定制序列化器接管 Map 全局行为(以 Jackson 为例)
当项目中多处需要统一规则(如全部下划线命名 + 时间 ISO 格式 + 过滤 null),应封装一个复用的 ObjectMapper 实例,而非每次手动处理 Map。
- 注册
SimpleModule,添加JsonSerializer:在 serialize 方法中,先 deep-copy 原 Map,再遍历 key-value,对 value 做类型分发处理(Date→String、Enum→code、null→跳过) - 配置 PropertyNamingStrategies.SNAKE_CASE,自动把
createTime→create_time - 设置
writeDatesAsTimestamps(false)关闭时间戳输出,强制走自定义格式器
避坑:Map 键非 String、循环引用、混合类型数组
这些看似边缘的问题,上线后极易引发静默失败或数据丢失。
- 确认 Map 的 key 全是
String—— 若用了Integer或CustomKey,Jackson 默认会 toString(),Gson 可能丢键;必要时先map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toString(), Map.Entry::getValue)) - 嵌套 Map/List 中若含循环引用(A→B→A),必须启用
SerializationFeature.FAIL_ON_SELF_REFERENCES并配合@JsonIdentityInfo,或提前用 IdentityHashMap 检测剪枝 - 数组中混用 String/Integer/Map(如
["a", 123, {"x":1}])是合法 JSON,但反序列化回 Map 时易因类型擦除出错;建议约定数组内元素类型一致,或用泛型容器包装











