本文目录导读:

- 📖 目录导读
- 为什么Java开发者必须掌握JSON解析?
- 主流JSON解析库横向对比
- 案例一:使用Jackson解析复杂嵌套JSON(最推荐方案)
- 案例二:使用Gson处理动态键名与泛型擦除问题
- 案例三:Fastjson在高性能场景下的正确打开方式
- 案例四:JSON转Java对象时如何处理日期/空值/枚举
- 案例五:解析流式JSON(大文件不OOM的秘诀)
- 高频问答:面试与实战中常见的8个JSON解析陷阱
- 总结:2025年应该选择哪个库?
📖 目录导读
- 为什么Java开发者必须掌握JSON解析?
- 主流JSON解析库横向对比(Jackson/Gson/Fastjson)
- 使用Jackson解析复杂嵌套JSON(最推荐方案)
- 使用Gson处理动态键名与泛型擦除问题
- Fastjson在高性能场景下的正确打开方式
- JSON转Java对象时如何处理日期/空值/枚举
- 解析流式JSON(大文件不OOM的秘诀)
- 高频问答:面试与实战中常见的8个JSON解析陷阱
- 2025年应该选择哪个库?
为什么Java开发者必须掌握JSON解析?
在目前的后端开发中,JSON已经彻底取代XML成为前后端数据交换的“通用语言”,无论是RESTful API、微服务调用、还是配置中心的动态配置,都离不开JSON的序列化与反序列化。一个不精通JSON解析的Java工程师,在面试中几乎不可能通过技术面,而实际开发中,解析错误导致的NullPointerException或ClassCastException更是让人头疼。
主流JSON解析库横向对比
| 库 | 性能 | 易用性 | 依赖体积 | 独有特性 |
|---|---|---|---|---|
| Jackson | 极快 | 中 | 轻量 | 流式API、树模型 |
| Gson | 快 | 极高 | 更轻 | 支持无注解字段映射 |
| Fastjson | 最快(基准测试) | 高 | 轻 | 自动识别类型 |
注意:Fastjson存在历史安全漏洞(如1.2.24之前的版本),推荐使用
fastjson2或全面转向Jackson/Gson。
案例一:使用Jackson解析复杂嵌套JSON(最推荐方案)
假设你有如下JSON(常见于订单系统):
{
"orderId": "10086",
"user": {"name": "张三", "age": 28, "vip": true},
"items": [
{"sku": "A001", "price": 99.5, "count": 2},
{"sku": "B002", "price": 199.0, "count": 1}
],
"address": null
}
实战代码(Java 8+):
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonNestedDemo {
public static void main(String[] args) throws Exception {
String json = "..."; // 上面那段JSON
ObjectMapper mapper = new ObjectMapper();
// 方式一:树模型(适合未知结构)
JsonNode root = mapper.readTree(json);
String orderId = root.get("orderId").asText();
String userName = root.path("user").path("name").asText();
boolean vip = root.path("user").path("vip").asBoolean();
System.out.println("用户: " + userName + ",VIP: " + vip);
// 方式二:绑定POJO(推荐用于已知结构)
Order order = mapper.readValue(json, Order.class);
double total = order.getItems().stream()
.mapToDouble(i -> i.getPrice() * i.getCount())
.sum();
System.out.println("订单" + order.getOrderId() + "总价: " + total);
}
}
关键点:使用.path()代替.get(),当字段不存在时返回NullNode而不是null,避免后续空指针。
案例二:使用Gson处理动态键名与泛型擦除问题
场景:解析{"data": {"user_1": {"name": "李四"}, "user_2": {"name": "王五"}}}——键名是动态的。
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.util.Map;
public class GsonDynamicKeyDemo {
public static void main(String[] args) {
String json = "{\"data\": {\"user_1\": {\"name\": \"李四\"}, \"user_2\": {\"name\": \"王五\"}}}";
Gson gson = new Gson();
// 解决泛型擦除:使用TypeToken
Map<String, Map<String, User>> map = gson.fromJson(json,
new TypeToken<Map<String, Map<String, User>>>(){}.getType());
Map<String, User> users = map.get("data");
users.forEach((k, v) -> System.out.println(k + " -> " + v.getName()));
}
static class User { private String name; }
}
案例三:Fastjson在高性能场景下的正确打开方式
若坚持使用Fastjson(或fastjson2),请务必注意:
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
public class Fastjson2Demo {
public static void main(String[] args) {
String json = "{\"code\":200,\"result\":{\"list\":[1,2,3]}}";
// fastjson2的API与旧版略有不同
JSONObject obj = JSON.parseObject(json);
int code = obj.getIntValue("code");
// 取出list后转成int数组
int[] list = obj.getJSONObject("result").getObject("list", int[].class);
System.out.println("code=" + code + ", sum=" + (list[0]+list[1]+list[2]));
}
}
安全警告:避免使用JSON.parseObject(json, Feature.SupportAutoType),除非你完全信任数据源。
案例四:JSON转Java对象时如何处理日期/空值/枚举
日期处理(Jackson):
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 或者禁用时间戳:mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
空值处理:默认情况下null字段会被丢弃,若想保留:
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); // 默认就是ALWAYS // 想忽略为null的字段:JsonInclude.Include.NON_NULL
枚举反序列化:默认按名字转换,若枚举值是小写:
public enum Status {
@JsonValue private final String code;
Status(String code) { this.code = code; }
@JsonCreator public static Status fromCode(String s) { ... }
}
案例五:解析流式JSON(大文件不OOM的秘诀)
当JSON文件超过500MB,一次性读入内存必然造成OOM,使用Jackson的流式API:
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
public class StreamingJsonDemo {
public static void main(String[] args) throws Exception {
JsonFactory factory = new JsonFactory();
try (JsonParser parser = factory.createParser(new File("big.json"))) {
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = parser.getCurrentName();
if ("items".equals(fieldName)) {
while (parser.nextToken() != JsonToken.END_ARRAY) {
// 每次只处理一个item对象,释放内存
if (parser.currentToken() == JsonToken.START_OBJECT) {
// 使用parser.readValueAs(Item.class) 或手动解析
}
}
}
}
}
}
}
高频问答:面试与实战中常见的8个JSON解析陷阱
Q1:Jackson解析时如何忽略未知字段?
添加
@JsonIgnoreProperties(ignoreUnknown = true)注解,或全局配置mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)。
Q2:Gson解析空字符串到int会报错吗?
会,Gson默认无法将空字符串转为int,需自定义
TypeAdapter或字段设为Integer类型。
Q3:Fastjson把0解析成false,怎么修复?
加
JSON.parseObject(json).getBooleanValue("flag")不行,需使用Integer类型手动判断。
Q4:JSON中有超大数字(超过long范围)怎么办?
Jackson默认会解析成
BigInteger,但若绑定到long字段会报错,建议声明字段为Long或BigInteger。
Q5:如何优雅地处理嵌套JSON的层级过深问题?
使用
JsonNode树模型时,层级过深会导致代码繁琐,可以写通用递归方法,或用XPath式路径(Jackson 2.15+的JsonNode.at("/user/address/city"))。
Q6:解析JSON时抛异常,调试思路是什么?
第一看异常栈指向的JSON位置(
at [Source: ...; line: 1, column: 11]);第二检查类型是否匹配(如JSON中是"123"字符串,而字段是int)。
Q7:两种库可以混用吗(如用Gson反序列化,Jackson序列化)?
某些场景可以(但性能损耗),如果追求一致性,推荐全项目统一用一个库。
Q8:API返回的JSON字段名是驼峰,但数据库字段是下划线,怎么映射?
Jackson:
@JsonProperty("user_name");Gson:@SerializedName("user_name")。
2025年应该选择哪个库?
- 团队新项目:强烈推荐Jackson,它功能最全、性能稳定、社区活跃(Spring Boot默认集成)。
- 简单小工具:Gson用起来最省心,代码量最少。
- 极端性能需求:fastjson2(不要用旧版),但必须升级到安全版本。
最后建议:如果你在写公司新项目,请直接使用Jackson搭配
jackson-databind+jackson-datatype-jsr310(处理Java 8时间),它是目前唯一既能处理复杂嵌套、又能流式解析、还不用担心中断更新的库。
行动提示:复制上面案例代码到你的IDE里跑一遍,把JSON换成你项目真实数据结构,立刻掌握解析技巧,如果遇到问题,欢迎在评论区留言详细错误信息,我会挑典型问题回复。