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;}// 序列化時輸出中文描述@JsonValuepublic String getDesc() {return desc;}// 反序列化時支持從數字或字符串解析@JsonCreatorpublic 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> {@Overridepublic 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)@JsonCreatorpublic 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); // 正常訪問