JSON
Jackson是java提供处理json数据序列化和反序列的工具类,在使用Jackson处理json前,我们得先掌握json。
JSON数据类型
类型 | 示例 | 说明 |
---|---|---|
字符串(String) | "hello" |
双引号包裹,支持转义字符(如 \n )。 |
数字(Number) | 42 , 3.14 , -1e5 |
整数、浮点数或科学计数法表示。 |
布尔值(Boolean) | true , false |
仅两个值,表示逻辑真/假。 |
对象(Object) | { "key": "value" } |
无序的键值对集合。 |
数组(Array) | [1, 2, 3] |
有序的值列表。 |
Null | null |
表示空值或占位符。 |
例如:
# 类型: Map<String,List<T>>
{
"B": [
{
"id": "1666364264118235829",
"sort": 1,
"originalPrice": "20.00"
},
{
"id": "1666364264118235829",
"sort": 2,
"originalPrice": "10.00"
},
{
"id": "1666364264118235829",
"sort": 3,
"originalPrice": "15.00"
}
]
}
# 类型:Map<String,T>
{
"A": {
"userWithdrawDayCountLimit": 2,
"userWithdrawDayAmountLimit": "100.00",
"userWithdrawTotalAmountLimit": "100.00",
"userPacketDayCountLimit": 100,
"platformWithdrawDayAmountLimit": "1000.00"
},
"B": {
"userWithdrawDayCountLimit": 3,
"userWithdrawDayAmountLimit": "100.00",
"userWithdrawTotalAmountLimit": "100.00",
"userPacketDayCountLimit": 100,
"platformWithdrawDayAmountLimit": "1000.00"
},
"D1": {
"userWithdrawDayCountLimit": 2,
"userWithdrawDayAmountLimit": "100.00",
"userWithdrawTotalAmountLimit": "100.00",
"userPacketDayCountLimit": 100,
"platformWithdrawDayAmountLimit": "1000.00"
}
}
#类型:T
{
"open": false,
"reachIndex": false,
"reachRemain": false,
"withdrawReachRemain": false,
"pushAcJoin": false,
"pushWithdraw": false
}
#类型:Map<String,T>
# T 包含 List<Map<BigDecimal, Double>> Map<BigDecimal, Double> Map<BigDecimal, Double> Integer
{
"A": {
"fixed": [{
"10.00": 1.00
}, {
"1.88": 1.00
}, {
"1.66": 1.00
}, {
"0.88": 1.00
}, {
"0.66": 1.00
}
],
"general": {
"0.01": 0.40,
"0.02": 0.25,
"0.03": 0.20,
"0.04": 0.10,
"0.05": 0.05
},
"big": {
"0.06": 0.10,
"0.08": 0.30,
"0.10": 0.40,
"0.15": 0.20
},
"generalToBig": 9
},
"B": {
"fixed": [{
"10.00": 1.00
}, {
"1.88": 1.00
}, {
"1.66": 1.00
}, {
"0.88": 1.00
}, {
"0.66": 1.00
}
],
"general": {
"0.01": 0.40,
"0.02": 0.25,
"0.03": 0.20,
"0.04": 0.10,
"0.05": 0.05
},
"big": {
"0.06": 0.10,
"0.08": 0.30,
"0.10": 0.40,
"0.15": 0.20
},
"generalToBig": 9
}
}
JSON易错点
大整数精度丢失
问题:JavaScript等语言使用双精度浮点数(64位)表示所有数字,超过
2^53
的整数无法精确表示。示例:
#JSON.parse 后会变成 9007199254740992(精度丢失) { "id": 9007199254740993 }
解决方案:将大整数以字符串传输,特别是开发场景中数据库主键ID采用雪花算法生成ID,很长,得用字符串
浮点数精度问题
问题:浮点数在不同系统间传输时可能因精度差异导致微小误差。
示例:
{ "price": 0.1 }
- 二进制浮点数:
0.1
无法精确表示,可能导致累加误差(如0.1 + 0.2 ≠ 0.3
),推荐使用BigDecimal类型。
Jackson
切入正题,在 Java 中使用 Jackson 库处理 JSON 时,数字类型的序列化(对象转JSON)和反序列化(JSON转对象)需要特别注意数据类型映射、精度问题和配置选项。
Jackson的使用
//反序列化
String outputJson = mapper.writeValueAsString(order);
//序列化
Order order = mapper.readValue(json, Order.class);
Jackson注解
注解 | 场景用途 |
---|---|
@JsonProperty |
映射 JSON 字段名 order_id 到 Java 字段 id 。 |
@JsonFormat |
将 id 序列化为字符串,createTime 格式化为指定日期格式。 |
@JsonCreator |
定义工厂方法,用于反序列化时构造 Order 对象。 |
@JsonValue |
序列化 OrderStatus 枚举时输出中文描述(而非枚举名称)。 |
@JsonDeserialize |
自定义 discountCode 字段的反序列化逻辑(从字符串提取数字)。 |
@JsonIgnore | 序列化和反序列化忽略该字段 |
用一个场景玩转这些注解~
假设订单对象
Order
包含以下需求:
订单号(id):后端字段为
Long
,但前端要求传输为字符串(避免大整数精度丢失)。下单时间(createTime):以
yyyy-MM-dd HH:mm:ss
格式传输。订单状态(status):枚举类型,序列化时输出中文描述,反序列化时支持数字和字符串。
自定义折扣码(discountCode):需要将字符串格式
"DISCOUNT-1001"
转换为纯数字1001
存储。订单创建方式:通过工厂方法反序列化 JSON。
完整代码实现
1. 订单状态枚举(使用 @JsonValue
和 @JsonCreator
)
public enum OrderStatus {
UNPAID(0, "未支付"),
PAID(1, "已支付"),
CANCELLED(2, "已取消");
private final int code;
private final String desc;
OrderStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
// 序列化时输出中文描述
@JsonValue
public String getDesc() {
return desc;
}
// 反序列化时支持从数字或字符串解析
@JsonCreator
public static OrderStatus from(Object value) {
if (value instanceof Integer) {
int code = (Integer) value;
for (OrderStatus status : values()) {
if (status.code == code) return status;
}
} else if (value instanceof String) {
String desc = (String) value;
for (OrderStatus status : values()) {
if (status.desc.equals(desc)) return status;
}
}
throw new IllegalArgumentException("无效的订单状态值: " + value);
}
}
2. 自定义折扣码反序列化器(使用 @JsonDeserialize
// 自定义反序列化逻辑:将 "DISCOUNT-1001" 转换为 1001
public class DiscountCodeDeserializer extends JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
String text = p.getText();
return Integer.parseInt(text.replace("DISCOUNT-", ""));
}
}
3. 订单对象(使用 @JsonFormat
、@JsonProperty
和 @JsonCreator
)
public class Order {
@JsonProperty("order_id") // JSON 字段名为 order_id,映射到 id 字段
@JsonFormat(shape = JsonFormat.Shape.STRING) // 序列化为字符串
private Long id;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private OrderStatus status;
@JsonDeserialize(using = DiscountCodeDeserializer.class) // 指定自定义反序列化器
private Integer discountCode;
// 使用工厂方法反序列化(@JsonCreator)
@JsonCreator
public static Order create(
@JsonProperty("order_id") Long id,
@JsonProperty("createTime") Date createTime,
@JsonProperty("status") OrderStatus status,
@JsonProperty("discountCode") Integer discountCode) {
Order order = new Order();
order.id = id;
order.createTime = createTime;
order.status = status;
order.discountCode = discountCode;
return order;
}
// Getter/Setter 省略
}
4. 测试序列化与反序列化
public class OrderExample {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 反序列化测试
String json = "{"
+ "\"order_id\": \"1234567890123456789\","
+ "\"createTime\": \"2023-10-01 14:30:00\","
+ "\"status\": 1," // 使用数字反序列化
+ "\"discountCode\": \"DISCOUNT-1001\""
+ "}";
Order order = mapper.readValue(json, Order.class);
System.out.println("反序列化结果: " + order.getStatus().getDesc()); // 输出: 已支付
System.out.println("折扣码: " + order.getDiscountCode()); // 输出: 1001
// 序列化测试
order.setStatus(OrderStatus.CANCELLED);
String outputJson = mapper.writeValueAsString(order);
System.out.println("序列化结果: " + outputJson);
// 输出: {"order_id":"1234567890123456789","createTime":"2023-10-01 14:30:00","status":"已取消","discountCode":1001}
}
}
Jackson的常见问题
Jackson 根据目标字段的类型自动推断数字类型,若类型不匹配可能抛出异常。例如:
public class Example {
private int value; // 目标字段类型
}
// JSON: {"value": 10000000000} (超过 int 范围)
// 反序列化时会抛出 `JsonMappingException: Numeric value (10000000000) out of range of int`
Java 的 Long
类型最大值为 9,223,372,036,854,775,807
,超过此值需用 BigInteger,例如:
public class BigNumberExample {
private BigInteger id; // 使用 BigInteger 接收大整数
}
// JSON: {"id": 123456789012345678901234567890}
//若目标字段为 Long 但值过大,会抛出 MismatchedInputException
double
或 float
类型可能导致精度丢失,使用 BigDecimal
替代
public class PrecisionExample {
@JsonFormat(shape = JsonFormat.Shape.STRING) // 以字符串形式传输
private BigDecimal price;
}
// JSON: {"price": "0.1"} (字符串形式避免二进制精度问题)
异常类型 | 原因 | 解决方案 |
---|---|---|
MismatchedInputException |
JSON 数字无法转换为目标类型(如溢出) | 使用更大的数据类型(如 Long → BigInteger ) |
InvalidFormatException |
数字格式错误(如非数字字符) | 校验输入数据或自定义反序列化逻辑 |
JsonParseException |
JSON 语法错误(如 1,23 代替 1.23 ) |
修复 JSON 格式 |
TypeReference类
使用 TypeReference
解决泛型类型擦除问题:
String json = "[{\"name\":\"Alice\"}, {\"name\":\"Bob\"}]";
List<User> users = mapper.readValue(json, new TypeReference<List<User>>() {});
假设需要将 JSON 数组 [{"name":"Alice"}, {"name":"Bob"}]
反序列化为 List<User>
String json = "[{\"name\":\"Alice\"}, {\"name\":\"Bob\"}]";
List<User> users = mapper.readValue(json, List.class); // 问题出现!
问题:由于泛型擦除,List.class
丢失了 User
的类型信息,Jackson 无法知道 List
中元素的类型,默认会反序列化为 List<LinkedHashMap>
,而非 List<User>
。
解决方案:
List<User> users = mapper.readValue(
json,
new TypeReference<List<User>>() {} // 匿名内部类
);
例子:
错误用法(未处理泛型擦除)
String json = "[{\"name\":\"Alice\"}]";
List<User> users = mapper.readValue(json, List.class); // 返回 List<LinkedHashMap>
User user = users.get(0); // 抛出 ClassCastException: LinkedHashMap 无法转为 User
正确用法(使用 TypeReference)
String json = "[{\"name\":\"Alice\"}]";
List<User> users = mapper.readValue(json, new TypeReference<List<User>>() {}); // 正确返回 List<User>
User user = users.get(0); // 正常访问