Fastjson高阶避坑指南从类型擦除到自定义序列化的深度实践如果你还在用JSONObject.parseObject()处理所有JSON转换需求那这篇文章可能会颠覆你的认知。上周我们团队刚解决了一个线上事故——因为Fastjson默认配置导致的日期格式不一致问题让三个微服务之间的数据交互彻底瘫痪了4小时。这不是我第一次见到Fastjson引发的生产事故但每次排查都让我对这个简单的JSON库产生新的敬畏。1. 泛型处理的类型擦除陷阱当你在Controller层写下ListUser users JSON.parseObject(jsonStr, List.class)时灾难的种子已经埋下。Fastjson在处理泛型时会遭遇Java的类型擦除机制这导致反序列化后的集合元素全部变成JSONObject而非你期望的User对象。典型问题重现String json [{\name\:\张三\},{\name\:\李四\}]; ListUser users JSON.parseObject(json, List.class); // 运行时抛出ClassCastException User firstUser users.get(0);正确解决方案// 方案1使用TypeReference保留泛型信息 ListUser users JSON.parseObject(json, new TypeReferenceListUser(){}); // 方案2Java 8的便捷写法 ListUser users JSON.parseArray(json, User.class);注意当处理多层嵌套泛型时如MapString, ListUser必须使用TypeReference。我们在订单系统中就遇到过将MapString, ListOrderItem反序列化为MapString, ListJSONObject导致业务逻辑异常的案例。2. 循环引用与$ref的相爱相杀对象间的双向关联是业务建模的常见需求但直接序列化会导致堆栈溢出。Fastjson默认启用循环引用检测后会生成$ref引用标记这虽然解决了溢出问题却可能引发其他兼容性问题。循环引用示例class Department { private ListEmployee employees; } class Employee { private Department department; } Department dept new Department(); Employee emp new Employee(); dept.setEmployees(Arrays.asList(emp)); emp.setDepartment(dept); String json JSON.toJSONString(dept); // 输出结果包含$ref // {employees:[{department:{$ref:$}}]}应对策略对比场景推荐配置优缺点纯Fastjson环境SerializerFeature.DisableCircularReferenceDetect避免$ref但需控制序列化深度多系统交互自定义SerializeFilter可精细控制但实现复杂前端展示需求使用DTO剥离关联结构清晰但增加转换成本我们在用户权限系统中采用的自定义方案JSON.toJSONString(dept, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue);3. 日期格式的暗礁区Fastjson的日期处理堪称配置地狱不同版本间的默认行为差异足以让人崩溃。我们曾因测试环境(1.2.54)和生产环境(1.2.83)版本不同导致日期格式不兼容引发批量任务失败。关键配置矩阵// 全局配置推荐初始化时设置 JSON.DEFFAULT_DATE_FORMAT yyyy-MM-dd HH:mm:ss; // 单次序列化指定格式 String json JSON.toJSONString(bean, SerializerFeature.WriteDateUseDateFormat); // 反序列化时指定格式 Model obj JSON.parseObject(json, Model.class, Feature.AllowISO8601DateFormat);日期处理的最佳实践所有微服务统一Fastjson版本显式声明JSONField(formatyyyy-MM-dd)注解跨系统传输时使用时间戳(long)而非格式字符串时区敏感场景强制指定TimeZone.getTimeZone(GMT8)4. 特殊字符的转义战争当JSON字符串包含换行符、emoji或HTML标签时默认的转义行为可能导致可读性灾难。某次我们处理用户输入的富文本内容时发现序列化后的JSON几乎不可读。转义控制方案// 禁用自动转义谨慎使用 JSON.DEFAULT_GENERATE_FEATURE | SerializerFeature.DisableCheckSpecialChar.getMask(); // 自定义转义处理器 SerializeWriter out new SerializeWriter(); out.config(SerializerFeature.DisableCheckSpecialChar, true); JSONSerializer serializer new JSONSerializer(out); serializer.write(content); String result out.toString();真实案例处理包含XML片段的JSON时我们最终采用BASE64编码方案JSONField(serializeUsing Base64Serializer.class) private String xmlContent;5. 版本升级的兼容性雷区从1.2.x升级到2.x版本是个充满陷阱的过程。我们花了两个月逐步迁移总结出这些经验重大变更应对表旧版本行为新版本变化迁移方案自动识别单引号严格双引号预处理替换字符宽松类型转换严格类型检查添加Feature.AllowArbitraryCommas默认关闭ASM默认启用ASM测试序列化性能差异无安全模式新增autoTypeFilter白名单配置安全配置示例# fastjson2安全配置 fastjson2.parser.autoTypeAcceptcom.ourpackage. fastjson2.parser.denyorg.apache.commons.collections4.6. 性能调优的隐藏参数在大数据量处理场景下这些配置可能带来5-10倍的性能提升// 初始化时配置单例模式 static { // 启用ASM字节码增强 ParserConfig.getGlobalInstance().setAsmEnable(true); // 缓存反序列化器 ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 预编译复杂对象的序列化器 SerializeConfig.getGlobalInstance().put(BigDecimal.class, new BigDecimalCodec()); } // 高频调用场景专用API String json JSON.toJSONString( data, SerializeConfig.getGlobalInstance(), SerializerFeature.WriteNonStringValueAsString, SerializerFeature.IgnoreNonFieldGetter );性能对比数据处理10万条订单数据配置组合耗时(ms)内存峰值(MB)默认参数1450320ASM缓存280110预编译序列化器190907. 自定义序列化的高阶玩法当标准序列化不能满足需求时Fastjson的扩展接口展现出强大灵活性。这是我们为金融系统设计的金额格式化方案public class MoneySerializer implements ObjectSerializer { Override public void write( JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { BigDecimal value (BigDecimal) object; String formatted value.setScale(2, RoundingMode.HALF_UP) .stripTrailingZeros() .toPlainString(); serializer.write(formatted); } } // 注册自定义序列化器 SerializeConfig.getGlobalInstance().put(BigDecimal.class, new MoneySerializer());扩展应用场景敏感数据脱敏枚举值特殊处理第三方类库适配二进制字段编码在监控系统中我们通过自定义序列化器实现了指标数据的压缩传输public class MetricSerializer implements ObjectSerializer { public void write(...) { MetricData data (MetricData) object; byte[] compressed Snappy.compress(data.toBytes()); serializer.write(Base64.getEncoder().encodeToString(compressed)); } }