Jackson使用详解

发布于:2025-05-18 ⋅ 阅读:(21) ⋅ 点赞:(0)

JSON

Jackson是java提供处理json数据序列化和反序列的工具类,在使用Jackson处理json前,我们得先掌握json。

JSON数据类型

类型 示例 说明
字符串(String) "hello" 双引号包裹,支持转义字符(如 \n)。
数字(Number) 423.14-1e5 整数、浮点数或科学计数法表示。
布尔值(Boolean) truefalse 仅两个值,表示逻辑真/假。
对象(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 包含以下需求:

  1. 订单号(id):后端字段为 Long,但前端要求传输为字符串(避免大整数精度丢失)。

  2. 下单时间(createTime):以 yyyy-MM-dd HH:mm:ss 格式传输。

  3. 订单状态(status):枚举类型,序列化时输出中文描述,反序列化时支持数字和字符串。

  4. 自定义折扣码(discountCode):需要将字符串格式 "DISCOUNT-1001" 转换为纯数字 1001 存储。

  5. 订单创建方式:通过工厂方法反序列化 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); // 正常访问


网站公告

今日签到

点亮在社区的每一天
去签到