【问题标题】:json deserialize in java for different type of variable in same objectjson在java中反序列化同一对象中不同类型的变量
【发布时间】:2022-01-28 17:08:47
【问题描述】:

我有两个 api,它们在 json 响应之后返回。

我创建了一个名为“Card”的类,我应该如何实现针对特定请求具有不同类型的“到期”字段。

提前致谢。

【问题讨论】:

  • 您可以展示您的尝试,如果您将回复发布为代码/文本块而不是图像会更好。您需要 2 个类,每个 API 一个,或者 Object 类型的字段,所以它可以是任何东西。
  • 不要使用多态性,它没有得到很好的支持。相反(如果你拥有合约)简化模型(例如添加expirityStringexpirityObject,如果两者都指定了不同的值,则抛出异常)。

标签: java json rest web-services deserialization


【解决方案1】:

为简单起见,我将仅使用示例中的卡片对象,我不会将其包装为另一个对象。为了避免示例中的重复,我将使用这个基类来保存公共字段:

public abstract class BaseCard {

    private String brand;
    private String fundingMethod;
    private String scheme;

    //setters and getters
}

你的选择很少。

选项 1: 如果您知道从哪个 api 获得响应,则可以有两个类,每个类都以特定于 api 的格式保存expiry。从 api 1 获取数据时使用:

public class CardApi1 extends BaseCard {

    private String expiry;

    //setters and getters
}

对于 api 2:

public class CardApi2 extends BaseCard {

    private Expiry expiry;

    //setters and getters
}

到期对象如下所示:

public class Expiry {

    private String month;
    private String year;

    //setters and getters
}

如果您正在调用 api 1,则反序列化为 CardApi1,否则为 CardApi2

选项2:设置过期字段Object,这样任何东西都可以反序列化到其中。

public class CardApiMixed extends BaseCard {

    private Object expiry;

    //setters and getters

    public String getExpiryAsString() {
        return (String) this.expiry;
    }

    public Map<String, Object> getExpiryAsMap() {
        @SuppressWarnings("unchecked")
        Map<String, Object> expiry = (Map<String, Object>) this.expiry;
        return expiry;
    }
}

这样,无论您调用哪个 api,您都可以反序列化为一个类。缺点是,在检索expiry的时候,还是要知道数据来自哪个api,所以要使用正确的方法。

选项 3: 编写自定义反序列化器,无论使用哪个 api,都能正确解析字段。我个人会选择这个选项。解串器是这样的:

public class ExpiryResolvingCardDeserializer extends StdDeserializer<CardApiResolved> {

    public ExpiryResolvingCardDeserializer() {
        super(CardApiResolved.class);
    }

    @Override
    public CardApiResolved deserialize(JsonParser parser, DeserializationContext context) throws IOException {
        JsonNode node = parser.getCodec().readTree(parser);
        CardApiResolved card = new CardApiResolved();
        card.setBrand(node.get("brand").asText());
        card.setFundingMethod(node.get("fundingMethod").asText());
        card.setScheme(node.get("scheme").asText());
        JsonNode expiryNode = node.get("expiry");
        Expiry expiry = new Expiry();
        if (expiryNode.isObject()) {
            //that's for api 2
            expiry.setMonth(expiryNode.get("month").asText());
            expiry.setYear(expiryNode.get("year").asText());
        } else {
            //for api 1
            String text = expiryNode.asText();
            //assuming format is always mmYY, you can handle it in differently if there are other options
            int splitIndex = 2;
            expiry.setMonth(text.substring(0, splitIndex));
            expiry.setYear(text.substring(splitIndex));
        }
        card.setExpiry(expiry);
        return card;
    }
}

总结一下,当expiry节点是对象时,像对象一样处理,从中获取月和年数据,如果是字符串,则将其拆分以提取月和年。像这样无论格式如何,expiry 总是被解析为Expiry 类。卡片类将如下所示:

@JsonDeserialize(using = ExpiryResolvingCardDeserializer.class)
public class CardApiResolved extends BaseCard {

    private Expiry expiry;

    //setters and getters
}

注意JsonDeserialize 注释指定了用于此类型的反序列化器。最后,一些单元测试可以使用并检查结果。 api 响应是测试资源中的文件。

public class CardApiTests {

    private final ObjectMapper mapper = new ObjectMapper();

    @Test
    public void testCardApi1() throws Exception {
        InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
        //map to CardApi1, when calling api 1
        CardApi1 result = this.mapper.readValue(inputStream, CardApi1.class);
        assertEquals("0139", result.getExpiry());
    }

    @Test
    public void testCardApi2() throws Exception {
        InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
        //map to CardApi2, when calling api 2
        CardApi2 result = this.mapper.readValue(inputStream, CardApi2.class);
        assertEquals("1", result.getExpiry().getMonth());
        assertEquals("39", result.getExpiry().getYear());
    }

    @Test
    public void testCardApiMixed_Api1() throws Exception {
        InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
        CardApiMixed result = this.mapper.readValue(inputStream, CardApiMixed.class);
        assertEquals("0139", result.getExpiryAsString());
    }

    @Test
    public void testCardApiMixed_Api2() throws Exception {
        InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
        CardApiMixed result = this.mapper.readValue(inputStream, CardApiMixed.class);
        assertEquals("1", result.getExpiryAsMap().get("month"));
        assertEquals("39", result.getExpiryAsMap().get("year"));
    }

    @Test
    public void testCardApiResolved_Api1() throws Exception {
        InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
        CardApiResolved result = this.mapper.readValue(inputStream, CardApiResolved.class);
        assertEquals("01", result.getExpiry().getMonth());
        assertEquals("39", result.getExpiry().getYear());
    }

    @Test
    public void testCardApiResolved_Api2() throws Exception {
        InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
        CardApiResolved result = this.mapper.readValue(inputStream, CardApiResolved.class);
        assertEquals("1", result.getExpiry().getMonth());
        assertEquals("39", result.getExpiry().getYear());
    }
}

【讨论】:

    【解决方案2】:

    您能否分享一下到期字段是如何准备的?喜欢

    "expiery":"0139" = 月+年。 如果字段是这样准备的,那么您可以保留字符串或列表的变量类型,您需要稍作修改。你会根据给定的例子给出一个想法。

       public class App {
        
            private static final HttpClient httpClient = HttpClient.newBuilder()
                    .version(HttpClient.Version.HTTP_1_1)
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();
        
            public static void main(String[] args) throws IOException, InterruptedException {
                HttpRequest request = HttpRequest.newBuilder()
                        .GET()
                        .uri(URI.create("https://jsonmock.abc.com/api/articles?page=3"))
                        .setHeader("User-Agent", "Java 11 HttpClient Bot") // add request header
                        .build();
        
                HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        
                // print response headers
                HttpHeaders headers = response.headers();
                headers.map().forEach((k, v) -> System.out.println(k + ":" + v));
                String responseBody = response.body();
                ObjectMapper mapper = new ObjectMapper();
                Object newJsonNode = mapper.readValue(responseBody, Object.class);
                Map<String, Object> objectMap = (Map) newJsonNode;
                if (objectMap.get("expiry") instanceof List) {
                    //if I assume that expiry string created by appending month and year
                    List<Map<String, Object>> list = (List) objectMap.get("expiry");
                    Map<String, Object> map = list.get(0);
                    String exp = (String) map.get("month") + (String) map.get("year");
                    objectMap.put("expiry", exp);
                    //then parse to direct object
                } else if (objectMap.get("expiry") instanceof String) {
                    //then cast to dto
                    //you can apply reverse logic here, i.e string to map
                }
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-05
      • 2012-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-15
      • 1970-01-01
      • 2020-08-21
      相关资源
      最近更新 更多