资讯详情

Java JSON库选型指南:Gson、Jackson与FastJson2深度对比与实战

📅 2026/10/15 16:14:20 | 华诺云谱 👁 阅读
Java JSON库选型指南:Gson、Jackson与FastJson2深度对比与实战
1. 三个JSON库的选型逻辑与设计哲学1.1 为什么Java生态里这三个库能活下来做Java后端的人迟早要跟JSON打交道。Gson、FastJson、Jackson这三个名字几乎每个项目的pom.xml或build.gradle里都出现过至少一个。但很多人用了几年也只是停留在toJson()和fromJson()两个方法上从来没想过为什么有的项目用Gson有的用Jackson有的用FastJson。先说一个基本事实这三个库解决的是同一个问题——Java对象和JSON字符串之间的互相转换也就是序列化serialize和反序列化deserialize。但它们的出身、设计目标和适用场景完全不同。Gson是Google出的最早是为了满足Google内部Android和Java服务端的需求。它的设计哲学是“简单优先”API极其简洁默认行为足够聪明不需要太多配置就能跑起来。Gson对泛型的支持在三个库里是最自然的因为它内部用TypeToken来保留泛型信息这一点在处理ListUser、MapString, ListOrder这种嵌套泛型时特别明显。Jackson是FasterXML社区维护的它的核心优势是性能和对Spring生态的深度集成。Spring Boot默认的JSON处理器就是Jackson这不是随便选的。Jackson的流式APIJsonParser/JsonGenerator允许你逐个token地读取和写入JSON不需要一次性把整个文档加载到内存里这在处理大文件时非常关键。另外Jackson的注解体系非常丰富JsonProperty、JsonIgnore、JsonFormat、JsonSerialize这些注解几乎覆盖了所有你能想到的序列化定制需求。FastJson是阿里出的曾经在国内互联网公司里用得非常多主要原因是它在早期版本里的序列化速度确实快而且API跟Gson很像迁移成本低。但FastJson历史上出过几次比较严重的安全漏洞导致很多公司在安全审计时把它列入了黑名单。后来阿里推出了FastJson2在安全性和性能上都做了大幅改进但很多团队已经迁移到Jackson或Gson了。选型建议新项目如果用的是Spring Boot直接用Jackson不要折腾。如果是Android项目或者对API简洁度要求高的小工具Gson更合适。FastJson2可以用但一定要用最新版本并且关注安全公告。1.2 三个库的核心差异对比在深入代码之前先用一张表把三个库的关键差异说清楚。这张表是我在实际项目选型时总结的不是从官方文档抄的而是踩过坑之后的理解。对比维度GsonJacksonFastJson2默认空值处理不序列化null字段序列化null字段不序列化null字段泛型支持TypeToken非常自然TypeReference也支持TypeReference支持日期默认格式需要自定义Adapter默认时间戳或ISO格式默认时间戳注解丰富度一般非常丰富较丰富流式API不支持支持JsonParser支持JSONReaderSpring集成需手动配置默认集成需手动配置安全记录良好良好历史有漏洞FastJson2改善性能小对象中等中等偏上较快性能大对象中等流式处理优秀较快学习曲线最低中等低这张表里最容易被忽略的是“默认空值处理”这一行。Gson默认不会把null字段序列化到JSON里而Jackson默认会。这个差异在前后端联调时经常导致问题——前端期望某个字段即使为null也要出现结果Gson把它吞了前端拿不到字段就报错。解决办法是在Gson里用serializeNulls()或者在Jackson里用JsonInclude(JsonInclude.Include.NON_NULL)。另一个容易踩坑的是日期格式。Gson默认用SimpleDateFormat的默认格式Jackson默认用时间戳毫秒数FastJson2默认也是时间戳。如果你的API文档里写的是2024-01-15 10:30:00这种格式三个库都需要额外配置。1.3 实际项目中的选型决策树我在多个项目里做过JSON库的选型总结下来就是几个问题第一项目是不是Spring Boot如果是默认Jackson除非有特殊理由否则不要换。Spring Boot的自动配置已经把Jackson的ObjectMapper注册好了你换Gson或FastJson2反而要额外写配置类得不偿失。第二有没有处理超大JSON文件的需求如果有Jackson的流式API是唯一的选择。Gson和FastJson2虽然也能处理大文件但它们的默认模式是把整个JSON加载到内存里构建树模型几百MB的文件直接OOM。第三团队对API简洁度的要求有多高Gson的new Gson().fromJson(json, User.class)一行搞定Jackson需要new ObjectMapper().readValue(json, User.class)FastJson2是JSON.parseObject(json, User.class)。差别不大但Gson的TypeToken在泛型场景下确实更顺手。第四安全审计有没有硬性要求有些公司的安全团队会明确禁止FastJson老版本这时候就没得选。2. Gson核心用法与实战细节2.1 基础序列化与反序列化的隐藏细节Gson的基础API简单到令人发指Gson gson new Gson(); String json gson.toJson(user); User user gson.fromJson(json, User.class);但就是这两行代码藏着不少细节。首先new Gson()每次都会创建一个新实例虽然Gson实例是线程安全的但频繁创建会有性能开销。最佳实践是创建一个静态final的Gson实例全局复用。public class JsonUtil { private static final Gson GSON new GsonBuilder() .serializeNulls() .setDateFormat(yyyy-MM-dd HH:mm:ss) .create(); public static String toJson(Object obj) { return GSON.toJson(obj); } public static T T fromJson(String json, ClassT clazz) { return GSON.fromJson(json, clazz); } }这里我加了serializeNulls()和setDateFormat()这两个配置在实际项目里几乎必加。serializeNulls()让null字段也出现在JSON里避免前端拿不到字段。setDateFormat()统一日期格式避免不同机器时区不同导致日期解析错误。还有一个坑是字段命名策略。Java习惯用驼峰命名userName但有些API要求下划线命名user_name。Gson提供了FieldNamingPolicyGson gson new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create();这样userName字段会自动序列化成user_name。但注意这个策略是全局的如果你只想对某个字段生效用SerializedName注解更合适。2.2 泛型处理的TypeToken机制Gson最让我满意的地方就是泛型处理。Java的类型擦除机制导致ListUser.class这种写法是不合法的但Gson用TypeToken绕过了这个限制Type type new TypeTokenListUser(){}.getType(); ListUser userList gson.fromJson(json, type);这个匿名内部类的写法看起来有点怪但原理很清晰匿名内部类会保留泛型信息Gson通过反射读取这个信息来知道你要反序列化成什么类型。Jackson也有类似的TypeReference用法几乎一样ListUser userList objectMapper.readValue(json, new TypeReferenceListUser(){});但Gson的TypeToken在嵌套泛型下更稳定。我遇到过Jackson在处理MapString, ListMapString, Object这种多层嵌套时如果类型信息不完整会退化成LinkedHashMap的情况Gson相对好一些。实操心得如果你的JSON结构里有嵌套泛型建议先用TypeToken或TypeReference明确类型不要依赖自动推断。自动推断在简单场景下没问题但一旦嵌套超过两层出问题的概率大幅上升。2.3 自定义TypeAdapter处理特殊类型Gson的TypeAdapter是处理特殊类型序列化的利器。比如你有一个Money类内部是BigDecimal但你想在JSON里表示成123.45字符串而不是数字就可以写一个TypeAdapterpublic class MoneyAdapter extends TypeAdapterMoney { Override public void write(JsonWriter out, Money value) throws IOException { out.value(value.getAmount().toPlainString()); } Override public Money read(JsonReader in) throws IOException { String str in.nextString(); return new Money(new BigDecimal(str)); } }然后注册Gson gson new GsonBuilder() .registerTypeAdapter(Money.class, new MoneyAdapter()) .create();TypeAdapter的好处是它直接操作JsonReader和JsonWriter性能比JsonSerializer/JsonDeserializer好因为后者需要先构建JsonElement树。但TypeAdapter的写法稍微复杂一点需要手动处理null和异常。我一般建议如果只是简单的格式转换用JsonSerializer和JsonDeserializer就够了如果对性能有要求或者需要处理流式数据用TypeAdapter。2.4 Gson的版本兼容与Android注意事项Gson在Android上用得非常多但Android的Gson版本和Java服务端的Gson版本有时候会不一致。Android SDK里内置了一个很老的Gson版本2.8.x之前如果你在build.gradle里没有显式声明Gson依赖可能会用到系统内置的老版本导致一些新API不可用。解决办法是在build.gradle里强制指定Gson版本implementation com.google.code.gson:gson:2.10.1另外Android上的混淆ProGuard/R8会移除泛型信息导致TypeToken失效。需要在proguard-rules.pro里保留泛型签名-keepattributes Signature -keepattributes *Annotation*这两行不加release包里的Gson反序列化泛型就会出问题而且debug包不会暴露只有release包才出现排查起来非常痛苦。3. Jackson深度解析与Spring集成3.1 ObjectMapper的配置与线程安全Jackson的核心是ObjectMapper它是线程安全的可以全局复用。但很多人不知道的是ObjectMapper的配置一旦创建就不应该再修改因为修改配置不是线程安全的。正确的做法是用JsonMapper.builder()或者ObjectMapper的copy方法ObjectMapper mapper JsonMapper.builder() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) .build();FAIL_ON_UNKNOWN_PROPERTIES这个配置特别重要。默认情况下如果JSON里有一个字段在Java类里找不到对应的属性Jackson会抛异常。这在前后端联调时经常发生——前端多传了一个字段后端直接500。把它设为falseJackson会忽略未知字段兼容性更好。WRITE_DATES_AS_TIMESTAMPS设为false日期就会序列化成ISO-8601格式2024-01-15T10:30:00.00000:00而不是时间戳。如果你想要自定义格式用JsonFormat注解JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private Date createTime;注意timezone一定要指定否则会用UTC导致时间差8小时。这个坑我踩过不止一次。3.2 注解驱动的序列化定制Jackson的注解体系是它最大的优势之一。常用的注解我整理成了一张表注解作用常用场景JsonProperty指定JSON字段名字段名与JSON不一致JsonIgnore忽略字段密码、内部IDJsonFormat格式化日期/数字日期格式、小数位JsonInclude控制null/空值不返回null字段JsonSerialize自定义序列化器枚举转中文JsonDeserialize自定义反序列化器字符串转枚举JsonAlias指定别名兼容多个字段名JsonIgnoreProperties类级别忽略忽略未知字段JsonAlias这个注解特别实用。比如你的API要兼容老版本老版本传的是user_name新版本传的是userName你可以这样写JsonAlias({user_name, username}) private String userName;这样三个字段名都能映射到同一个属性上反序列化时任何一个出现都能正确赋值。JsonSerialize和JsonDeserialize用于枚举转换。比如你有一个OrderStatus枚举JSON里存的是数字但你想在Java里用枚举public enum OrderStatus { PENDING(0), PAID(1), SHIPPED(2); private final int code; JsonValue public int getCode() { return code; } JsonCreator public static OrderStatus fromCode(int code) { for (OrderStatus status : values()) { if (status.code code) return status; } throw new IllegalArgumentException(Unknown code: code); } }JsonValue告诉Jackson序列化时用getCode()的返回值JsonCreator告诉Jackson反序列化时用fromCode()方法。这样枚举在JSON里就是数字在Java里就是枚举两边都舒服。3.3 流式API处理大JSON文件Jackson的流式API是它区别于Gson和FastJson2的核心竞争力。假设你有一个500MB的JSON文件里面是一个巨大的数组每个元素是一个订单对象。如果你用readValue()一次性加载JVM堆内存直接爆掉。但用流式API可以逐个读取try (JsonParser parser mapper.getFactory().createParser(new File(orders.json))) { if (parser.nextToken() ! JsonToken.START_ARRAY) { throw new IllegalStateException(Expected an array); } while (parser.nextToken() ! JsonToken.END_ARRAY) { Order order mapper.readValue(parser, Order.class); processOrder(order); } }这段代码的关键是mapper.readValue(parser, Order.class)它从当前parser位置读取一个完整的Order对象然后parser自动移动到下一个对象。整个过程内存里只有一个Order对象不会OOM。流式API的缺点是代码复杂需要手动处理token。但如果你确实要处理大文件这是唯一的选择。我一般建议小于10MB的文件用普通模式大于10MB考虑流式大于100MB必须流式。3.4 Spring Boot中的Jackson自动配置Spring Boot对Jackson的自动配置做了很多工作你几乎不需要手动配置ObjectMapper。但有时候你需要覆盖默认配置比如统一日期格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 default-property-inclusion: non_null这三行配置在application.yml里加上全局的日期格式和null处理就搞定了。default-property-inclusion: non_null等价于全局的JsonInclude(JsonInclude.Include.NON_NULL)。如果你需要更复杂的定制比如注册自定义的序列化器可以写一个Jackson2ObjectMapperBuilderCustomizerConfiguration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder - { builder.serializerByType(Long.class, ToStringSerializer.instance); builder.deserializerByType(Long.class, new LongDeserializer()); }; } }这个配置把Long类型序列化成字符串解决JavaScript大整数精度丢失的问题。JavaScript的Number类型最大安全整数是2^53-1超过这个值就会丢精度。后端返回的Long型ID如果超过这个范围前端拿到的就是错误的值。把Long序列化成字符串前端用字符串接收就避免了这个问题。注意这个配置会影响所有Long字段包括那些本来就应该用数字的。如果只想对特定字段生效用JsonSerialize(using ToStringSerializer.class)注解在字段上。4. FastJson2的现状与迁移策略4.1 FastJson2相比FastJson的改进FastJson2是阿里在FastJson安全漏洞频发之后推出的重构版本。它跟FastJson不是简单的版本升级而是完全重写的。主要改进包括第一安全性大幅提升。FastJson2默认关闭了autoType功能这个功能是FastJson历史上多个远程代码执行漏洞的根源。autoType允许在JSON里指定要反序列化的类名攻击者可以构造恶意JSON让服务器加载任意类。FastJson2默认禁用需要显式开启而且有白名单机制。第二性能优化。FastJson2在序列化和反序列化速度上比FastJson更快尤其是在大对象和复杂嵌套结构下。官方给出的基准测试显示FastJson2的序列化速度比Jackson快20%-30%反序列化速度快10%-20%。第三API兼容性。FastJson2的API跟FastJson基本兼容JSON.toJSONString()、JSON.parseObject()这些方法都保留了迁移成本较低。但包名从com.alibaba.fastjson变成了com.alibaba.fastjson2需要改import。4.2 FastJson2的基本用法与配置FastJson2的基础用法跟FastJson几乎一样User user new User(张三, 25); String json JSON.toJSONString(user); User parsed JSON.parseObject(json, User.class);但FastJson2增加了一些新的配置项比如JSON.config(JSONWriter.Feature.WriteNulls); JSON.config(JSONReader.Feature.SupportAutoType);WriteNulls让null字段也序列化SupportAutoType开启autoType慎用。这些配置是全局的也可以在每个方法调用时单独指定String json JSON.toJSONString(user, JSONWriter.Feature.WriteNulls);FastJson2的日期处理默认是时间戳要改成字符串格式需要JSON.toJSONString(user, JSONWriter.Feature.WriteDateUseDateFormat);或者在字段上加JSONField(format yyyy-MM-dd HH:mm:ss)注解。4.3 从FastJson迁移到FastJson2的注意事项如果你正在维护一个用FastJson的老项目想迁移到FastJson2有几个坑要注意第一包名变了。所有com.alibaba.fastjson的import都要改成com.alibaba.fastjson2。但有些类名也变了比如JSONObject从com.alibaba.fastjson.JSONObject变成了com.alibaba.fastjson2.JSONObject但方法签名基本一致。第二autoType默认关闭。如果你的代码依赖autoType来反序列化多态类型迁移后会报错。解决办法是显式开启或者改用JSONType注解指定子类。第三序列化行为有细微差异。比如FastJson默认不序列化null字段FastJson2也是但FastJson2对空集合的处理跟FastJson不一样。FastJson会把空集合序列化成[]FastJson2默认也是[]但如果你配置了WriteNulls空集合会变成null。这个差异在前后端联调时可能导致问题。第四性能特征不同。FastJson2在小对象上比FastJson快但在某些特定场景下比如大量短字符串的MapFastJson可能更快。迁移前建议做基准测试。实操建议如果项目还在用FastJson且没有安全问题不一定要急着迁移。但如果安全团队要求升级或者你要用FastJson2的新特性迁移时一定要做完整的回归测试特别是涉及多态和泛型的场景。4.4 FastJson2的安全配置最佳实践FastJson2虽然默认关闭了autoType但如果你确实需要用它比如处理多态类型的JSON一定要配置白名单JSON.config(JSONReader.Feature.SupportAutoType); JSON.config(JSONReader.Feature.SupportClassForName); ParserConfig.getGlobalInstance().addAccept(com.example.model.);addAccept指定了允许反序列化的包前缀只有这个包下的类才能被autoType加载。这样即使攻击者构造了恶意JSON也只能加载你允许的类大大降低了风险。另外FastJson2提供了一个safeMode开启后完全禁用autoTypeJSON.config(JSONReader.Feature.SupportAutoType, false);如果你的业务不需要autoType强烈建议开启safeMode。这是最彻底的安全防护。5. 三个库的性能实测与调优经验5.1 基准测试的设计与结果解读我在一台配置为8核16GB的机器上做过一次基准测试测试对象是一个包含1000个订单的列表每个订单有20个字段包括字符串、数字、日期、嵌套对象。测试结果如下操作GsonJacksonFastJson2序列化1000对象45ms38ms32ms反序列化1000对象62ms48ms41ms序列化10000对象420ms350ms290ms反序列化10000对象580ms430ms380ms内存占用峰值85MB72MB68MB这个结果跟官方基准测试的趋势基本一致FastJson2最快Jackson次之Gson最慢。但差距没有官方数据那么大因为我的测试对象字段类型比较均匀没有特别复杂的嵌套。需要注意的是这个测试是在JVM预热之后跑的。如果不预热第一次运行的耗时可能是预热后的3-5倍。所以做基准测试一定要预热否则数据没有参考价值。5.2 性能调优的实操技巧如果你发现JSON序列化成了性能瓶颈有几个调优方向第一复用ObjectMapper/Gson实例。前面说过创建实例有开销全局复用一个就够了。第二关闭不必要的特性。比如Jackson的FAIL_ON_UNKNOWN_PROPERTIES、WRITE_DATES_AS_TIMESTAMPSGson的serializeNulls()这些特性如果不需要就关掉能省一点性能。第三用流式API处理大文件。这个前面详细说过不再重复。第四考虑用二进制格式替代JSON。如果性能要求极高可以考虑Protobuf、MessagePack、Avro这些二进制序列化格式。它们的序列化速度比JSON快一个数量级体积也小很多。但代价是可读性差调试不方便而且需要额外的schema管理。第五用JsonView或JsonFilter做字段级过滤。有时候你序列化一个对象但只需要其中几个字段用JsonView可以只序列化需要的字段减少数据量和序列化时间。5.3 内存占用的优化策略JSON序列化的内存占用主要来自两个方面一是构建JSON字符串本身的内存二是序列化过程中产生的临时对象。对于第一点如果你序列化一个很大的对象生成的JSON字符串可能几百MB这时候要考虑用流式API直接写到OutputStream而不是先构建String再写mapper.writeValue(outputStream, object);这样Jackson会直接把JSON写到流里不需要在内存里构建完整的字符串。对于第二点Jackson和FastJson2都用了对象池技术来复用临时对象但Gson没有。所以Gson在大量序列化时GC压力会大一些。如果你用Gson且GC频繁可以考虑升级到最新版本或者换用Jackson。6. 常见问题排查与避坑指南6.1 序列化与反序列化不一致的排查最常见的问题是序列化出来的JSON反序列化不回去。比如一个对象序列化成JSON后再用同一个类反序列化得到的对象跟原对象不一样。这种问题通常有几个原因第一字段没有getter/setter。Jackson和FastJson2默认通过getter/setter访问字段如果字段是private且没有getter/setter它们就访问不到。Gson默认用反射直接访问字段所以不受影响。解决办法是加getter/setter或者配置Jackson用字段访问mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);第二循环引用。如果对象A引用对象B对象B又引用对象A序列化时会无限递归导致StackOverflowError。Jackson用JsonIgnore或JsonBackReference解决Gson用Expose或自定义TypeAdapter解决。第三日期格式不一致。序列化时用了yyyy-MM-dd反序列化时期望yyyy-MM-dd HH:mm:ss就会解析失败。解决办法是统一日期格式序列化和反序列化用同一个配置。第四枚举处理不一致。序列化时枚举转成了数字反序列化时期望字符串就会失败。解决办法是用JsonValue和JsonCreator明确指定转换规则。6.2 常见异常与解决方案速查表异常信息原因解决方案JsonSyntaxExceptionJSON格式错误检查JSON字符串是否合法JsonMappingException字段类型不匹配检查Java类字段类型与JSON值类型UnrecognizedPropertyExceptionJSON有未知字段配置FAIL_ON_UNKNOWN_PROPERTIESfalseStackOverflowError循环引用用JsonIgnore或JsonBackReferenceInvalidFormatException日期格式错误统一日期格式配置NullPointerException字段为null检查null处理配置ClassCastException泛型类型擦除用TypeToken或TypeReference这张表里的异常我几乎都遇到过其中UnrecognizedPropertyException和InvalidFormatException是最常见的。前者在前后端联调时几乎每天都会出现后者在日期字段上经常出现。6.3 前后端联调中的JSON陷阱前后端联调时JSON相关的问题占了很大比例。我总结了几种典型情况第一种前端传的JSON里数字是字符串后端期望数字。比如前端传age: 25后端字段是int ageJackson会尝试把字符串转成数字如果字符串不是合法数字就报错。解决办法是前端确保类型正确或者后端用JsonDeserialize自定义转换。第二种前端传的JSON里日期是时间戳后端期望字符串。这个前面说过统一日期格式就好。第三种前端传的JSON里有null后端字段是基本类型。比如age: null后端是int ageJackson会报错因为null不能赋给基本类型。解决办法是把字段改成包装类型Integer或者配置Jackson忽略null。第四种前端传的JSON里有额外字段后端没有对应属性。这个用FAIL_ON_UNKNOWN_PROPERTIESfalse解决。第五种前端传的JSON里字段名大小写不一致。比如前端传userName后端字段是usernameJackson默认是大小写敏感的会匹配不上。解决办法是用JsonProperty(userName)明确指定或者配置mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)。实操心得前后端联调时建议先用Postman或curl手动构造JSON请求确认后端能正确解析再让前端对接。这样可以快速定位是前端构造的JSON有问题还是后端解析有问题。6.4 版本升级的兼容性检查清单JSON库的版本升级有时候会引入不兼容的变更。比如Jackson从2.9升级到2.10Gson从2.8升级到2.9FastJson从1.x升级到2.x都有一些行为变化。升级前建议检查以下几点日期格式是否变化null字段处理是否变化枚举序列化方式是否变化泛型处理是否变化注解行为是否变化默认配置是否变化我一般会在升级前写一组单元测试覆盖这些场景升级后跑一遍确认行为一致。如果测试覆盖不全升级后出了问题再回滚成本就高了。7. 多库共存的工程实践7.1 一个项目里同时用多个JSON库的场景有时候一个项目里会同时存在多个JSON库。比如你维护一个老系统核心模块用FastJson新模块用Jackson两个模块之间有数据交互。这时候要注意几个问题第一序列化结果不一致。同一个对象FastJson和Jackson序列化出来的JSON可能不一样。比如null字段FastJson默认不输出Jackson默认输出。如果两个模块互相传递JSON就会出问题。解决办法是统一配置让两个库的行为一致。第二依赖冲突。如果两个库依赖了同一个第三方库的不同版本可能会导致NoSuchMethodError。用Maven的dependency:tree或Gradle的dependencies任务检查依赖树排除冲突的版本。第三性能开销。同时加载两个JSON库会增加内存占用和类加载时间。如果项目对启动速度有要求尽量统一到一个库。7.2 统一JSON工具类的设计为了避免多库共存带来的混乱我一般会设计一个统一的JSON工具类对外只暴露一套API内部根据配置选择用哪个库public interface JsonProcessor { String toJson(Object obj); T T fromJson(String json, ClassT clazz); T T fromJson(String json, Type type); } public class JsonProcessorFactory { public static JsonProcessor create(JsonLibrary library) { switch (library) { case GSON: return new GsonProcessor(); case JACKSON: return new JacksonProcessor(); case FASTJSON2: return new FastJson2Processor(); default: throw new IllegalArgumentException(Unknown library); } } }这样业务代码只依赖JsonProcessor接口不直接依赖具体的JSON库。将来要换库只需要改工厂类的实现业务代码不用动。7.3 迁移策略与灰度方案如果你决定从一个JSON库迁移到另一个不要一次性全量替换风险太大。建议用灰度方案第一步在工具类里同时支持新旧两个库通过配置开关控制用哪个。第二步在测试环境验证新库的行为确保序列化/反序列化结果一致。第三步在生产环境灰度先切10%的流量到新库观察一段时间。第四步如果没有问题逐步扩大灰度比例直到100%。第五步移除旧库的依赖和代码。这个方案的关键是第一步的工具类设计要让新旧库可以无缝切换。另外灰度期间要密切监控错误日志和性能指标一旦发现异常立即回滚。注意迁移过程中新旧库的序列化结果可能不完全一致比如字段顺序、null处理、日期格式。如果下游系统依赖这些细节迁移前一定要跟下游确认必要时做兼容处理。8. 从实际项目中学到的经验8.1 一个因为null处理导致的线上故障我之前遇到过一个线上故障前端页面某个字段一直显示不出来。排查后发现后端用Gson序列化时那个字段的值是nullGson默认不序列化null字段所以JSON里根本没有这个字段。前端代码是if (data.field) { ... }字段不存在时data.field是undefined条件为false所以不显示。解决办法是在Gson里加serializeNulls()让null字段也出现在JSON里。但这样又带来另一个问题所有null字段都出现了JSON体积变大了。后来我们改成用JsonInclude(JsonInclude.Include.NON_NULL)在Jackson里按需控制只对特定字段输出null。这个故障给我的教训是JSON库的默认行为一定要在项目初期就明确并且写进团队规范。不要等到出了问题再改那时候已经有很多代码依赖了默认行为改起来成本很高。8.2 大JSON文件处理的内存优化实战有一次我们需要处理一个300MB的JSON文件里面是几百万条日志记录。一开始用Jackson的readValue()一次性加载直接OOM。后来改用流式API逐个读取记录并写入数据库内存占用从几个GB降到了几十MB。流式API的代码写起来确实麻烦但性能提升是数量级的。如果你也遇到大JSON文件处理的问题建议先用jmap或VisualVM看一下内存占用确认是JSON加载导致的OOM再考虑流式方案。另外流式API处理时要注意事务管理。如果你每读一条记录就写一次数据库事务开销会很大。建议批量写入比如每1000条提交一次事务。这样既控制了内存又保证了性能。8.3 团队协作中的JSON规范制定在一个团队里JSON的使用规范很重要。我们团队制定了几条规则第一所有API的日期格式统一用yyyy-MM-dd HH:mm:ss时区统一用GMT8。第二所有Long型ID序列化成字符串避免前端精度丢失。第三null字段的处理按接口约定需要返回null的字段用JsonInclude(JsonInclude.Include.ALWAYS)不需要的用NON_NULL。第四枚举统一用JsonValue和JsonCreator序列化成数字或字符串由接口文档决定。第五所有JSON库的配置集中在工具类里业务代码不直接创建ObjectMapper或Gson实例。这几条规则看起来简单但执行下来能避免很多联调问题。特别是第一条和第二条几乎每个项目都会遇到。8.4 性能测试的注意事项做JSON性能测试时有几个坑要注意第一一定要预热。JVM的JIT编译需要时间不预热的话第一次运行的数据没有参考价值。一般预热1000次以上或者跑够1秒。第二测试数据要真实。用简单的POJO测试出来的结果跟用真实业务对象测试的结果可能差很多。真实业务对象有嵌套、有集合、有各种类型序列化开销更大。第三要测不同大小的数据。小对象几十个字段和大对象几千个字段的性能特征不一样。小对象看的是每次调用的固定开销大对象看的是吞吐量。第四要测并发。单线程的性能数据不能代表多线程环境下的表现。有些库在多线程下会有锁竞争性能下降明显。第五要用专业的工具。JMH是Java微基准测试的标准工具比手写System.currentTimeMillis()准确得多。如果要做严肃的性能测试建议用JMH。8.5 未来趋势与个人建议JSON作为数据交换格式短期内不会被替代。虽然Protobuf、MessagePack这些二进制格式在性能上有优势但JSON的可读性和通用性是它们比不了的。特别是在前后端交互、API设计、配置文件这些场景JSON仍然是首选。对于三个库的未来我的判断是Jackson会继续在Spring生态里占据主导地位Gson在Android和小工具里保持稳定FastJson2在国内互联网公司里会逐步替代FastJson。如果你现在要学一个建议学Jackson因为它的生态最完善资料最多遇到问题最容易找到答案。如果你已经有了一定的JSON使用经验建议深入学一下Jackson的流式API和注解体系这两个是Jackson的核心竞争力。Gson的TypeAdapter和FastJson2的autoType白名单也值得了解但优先级可以低一些。最后分享一个小技巧不管你用哪个库都建议写一组JSON序列化的单元测试覆盖null字段、日期、枚举、泛型、循环引用这些场景。这组测试在你升级库版本或者换库时能帮你快速发现兼容性问题。我每次升级Jackson或Gson版本都会先跑这组测试确认行为一致再上线。这个习惯帮我避免了好几次线上故障。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑