使用Jackson自定义反序列化器处理美团API中不规范JSON字段的兼容方案

使用Jackson自定义反序列化器处理美团API中不规范JSON字段的兼容方案

在对接美团开放平台API时,开发者常遇到返回JSON结构不规范的问题:例如字段类型不一致(字符串与数字混用)、布尔值以"1"/"0"表示、时间格式非ISO8601、甚至同一字段在不同接口下含义不同。若直接使用标准Jackson反序列化,极易抛出JsonMappingException。本文将基于baodanbao.com.cn.*包结构,展示如何通过自定义JsonDeserializer实现健壮的兼容解析。

1. 问题场景示例

美团某订单接口返回如下片段:

{
  "order_id": "123456",
  "is_valid": "1",
  "amount": "89.50",
  "create_time": "2024-03-05 14:30:00"
}

理想Java类应为:

public class MeituanOrder {
    private Long orderId;
    private Boolean isValid;
    private BigDecimal amount;
    private LocalDateTime createTime;
}

但默认反序列化会因类型不匹配失败。需定制逻辑。
在这里插入图片描述

2. 自定义Boolean反序列化器

处理 "1"/"0""true"/"false" 混合情况:

package baodanbao.com.cn.deserializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;

public class FlexibleBooleanDeserializer extends JsonDeserializer<Boolean> {

    @Override
    public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String value = p.getText().trim();
        if (value.isEmpty()) return null;
        if ("1".equals(value) || "true".equalsIgnoreCase(value)) {
            return true;
        }
        if ("0".equals(value) || "false".equalsIgnoreCase(value)) {
            return false;
        }
        throw new IllegalArgumentException("无法解析布尔值: " + value);
    }
}

3. 自定义数值反序列化器(支持字符串转BigDecimal)

package baodanbao.com.cn.deserializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;
import java.math.BigDecimal;

public class StringToBigDecimalDeserializer extends JsonDeserializer<BigDecimal> {

    @Override
    public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String value = p.getText().trim();
        if (value.isEmpty()) return null;
        try {
            return new BigDecimal(value);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("无法解析金额: " + value, e);
        }
    }
}

4. 自定义LocalDateTime反序列化器(兼容空格分隔时间)

package baodanbao.com.cn.deserializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public class FlexibleLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String value = p.getText().trim();
        if (value.isEmpty()) return null;
        try {
            return LocalDateTime.parse(value, FORMATTER);
        } catch (DateTimeParseException e) {
            throw new IllegalArgumentException("无法解析时间: " + value, e);
        }
    }
}

5. 在实体类中应用自定义反序列化器

package baodanbao.com.cn.model.meituan;

import baodanbao.com.cn.deserializer.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import java.math.BigDecimal;
import java.time.LocalDateTime;

public class MeituanOrder {

    @JsonDeserialize(using = StringToLongDeserializer.class)
    private Long orderId;

    @JsonDeserialize(using = FlexibleBooleanDeserializer.class)
    private Boolean isValid;

    @JsonDeserialize(using = StringToBigDecimalDeserializer.class)
    private BigDecimal amount;

    @JsonDeserialize(using = FlexibleLocalDateTimeDeserializer.class)
    private LocalDateTime createTime;

    // getters and setters
}

其中StringToLongDeserializer实现类似:

package baodanbao.com.cn.deserializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;

public class StringToLongDeserializer extends JsonDeserializer<Long> {
    @Override
    public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String text = p.getText().trim();
        return text.isEmpty() ? null : Long.parseLong(text);
    }
}

6. 全局注册(可选)

若多个类需复用,可在ObjectMapper中全局注册:

package baodanbao.com.cn.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Boolean.class, new FlexibleBooleanDeserializer());
        module.addDeserializer(BigDecimal.class, new StringToBigDecimalDeserializer());
        mapper.registerModule(module);
        return mapper;
    }
}

但注意:全局注册可能影响其他接口,建议优先使用字段级@JsonDeserialize

7. 异常容错增强:忽略非法字段

在配置中开启宽容模式,避免因个别字段异常导致整个响应解析失败:

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
    mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
    // 注册模块...
    return mapper;
}

8. 单元测试验证

package baodanbao.com.cn.test;

import baodanbao.com.cn.model.meituan.MeituanOrder;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class MeituanOrderDeserializationTest {

    @Test
    void testDeserializeFromMeituanJson() throws Exception {
        String json = """
            {
              "order_id": "789012",
              "is_valid": "1",
              "amount": "123.45",
              "create_time": "2024-03-05 14:30:00"
            }
            """;

        ObjectMapper mapper = new ObjectMapper();
        MeituanOrder order = mapper.readValue(json, MeituanOrder.class);

        assertEquals(789012L, order.getOrderId());
        assertTrue(order.getIsValid());
        assertEquals(new BigDecimal("123.45"), order.getAmount());
        assertEquals(LocalDateTime.of(2024, 3, 5, 14, 30, 0), order.getCreateTime());
    }
}

本文著作权归吃喝不愁app开发者团队,转载请注明出处!

使用 Jackson 进行 JSON 反序列化时,如果 JSON 数据中缺少 Java 实体类中的某些字段,默认情况下这些字段会被设置为 `null`(对于对象类型)或默认值(如 `int` 类型的字段会被设为 `0`)。然而,如果你希望在反序列化过程中忽略那些存在于 JSON 中的字段,而是将其设为默认值或 `null`,可以通过以下几种方式进行配置: ### 1. 使用 `@JsonIgnoreProperties` 注解 可以在类级别上使用 `@JsonIgnoreProperties(ignoreUnknown = true)` 来忽略 JSON 中未定义的字段。该注解还可以指定一组需要忽略的字段名称。 ```java @JsonIgnoreProperties(ignoreUnknown = true) public class User { private String name; private int age; // Getters and Setters } ``` 这种方式适用于全局忽略未知字段[^2]。 --- ### 2. 配置 `ObjectMapper` 忽略未知字段 通过自定义 `ObjectMapper` 实例并启用 `DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES` 特性来控制是否忽略 JSON 中多余的字段。 ```java ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); ``` 这样配置后,即使 JSON 中包含实体类中存在的字段,也会抛出异常,同时这些字段会被映射到对象中[^2]。 --- ### 3. 使用 `@JsonProperty` 指定字段访问策略 如果某个字段JSON 中可能缺失,并且你希望在反序列化时跳过该字段设置默认值,可以结合 `@JsonProperty` 和自定义逻辑进行处理。 例如,你可以为字段添加 `@JsonProperty(required = false)`,但这仅在使用 Jackson 的模块化扩展时有效,如与 `jackson-module-parameter-names` 结合使用。 --- ### 4. 自定义反序列化器(Deserializer) 对于更复杂的场景,比如字段缺失时需要执行特定逻辑(如从其他字段推导),可以实现自定义的 `JsonDeserializer` 并注册到 `ObjectMapper` 中。 ```java public class CustomUserDeserializer extends JsonDeserializer<User> { @Override public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.getCodec().readTree(p); User user = new User(); if (node.has("name")) { user.setName(node.get("name").asText()); } // 忽略 age 字段处理 return user; } } ``` 然后注册该反序列化器: ```java SimpleModule module = new SimpleModule(); module.addDeserializer(User.class, new CustomUserDeserializer()); objectMapper.registerModule(module); ``` --- ### 5. 使用 `@JsonInclude` 控制序列化行为(间接影响反序列化逻辑) 虽然 `@JsonInclude` 主要用于控制序列化输出,但配合其他配置可以间接影响反序列化时的行为。例如,设置 `@JsonInclude(Include.NON_NULL)` 可以避免将 `null` 值写入 JSON,从而保持数据简洁。 ```java @JsonInclude(JsonInclude.Include.NON_NULL) public class User { private String name; private Integer age; // 使用包装类型以支持 null // Getters and Setters } ``` --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值