Jackson - 将内部对象列表反序列化为更高级别的列表

Posted

技术标签:

【中文标题】Jackson - 将内部对象列表反序列化为更高级别的列表【英文标题】:Jackson - deserialize inner list of objects to list of one higher level 【发布时间】:2019-06-22 18:55:45 【问题描述】:

使用 Spring Boot 和 Jackson,如何将包装/内部列表直接反序列化为外部级别的列表?

例如,我有:


    "transaction": 
    "items": 
        "item": [
            
                "itemNumber": "193487654",
                "itemDescription": "Widget",
                "itemPrice": "599.00",
                "itemQuantity": "1",
                "itemBrandName": "ACME",
                "itemCategory": "Electronics",
                "itemTax": "12.95"
            ,
            
                "itemNumber": "193487654",
                "itemDescription": "Widget",
                "itemPrice": "599.00",
                "itemQuantity": "1",
                "itemBrandName": "ACME",
                "itemCategory": "Electronics",
                "itemTax": "12.95"
            
        ]
    ,
    ...
    

在JSON中,itemitems下的一个列表;但我想将它解析为一个名为items 的列表,直接在transaction 下,而不是定义一个包含名为item 的列表的DTO Items

这可能吗?如何定义这个 DTO Item

public class TrasactionDTO 
    private List<Item> items;
    ...


public class Item 


这个问题类似,但不能解决问题。 Deserialize wrapped list using Jackson

【问题讨论】:

无法修改 JSON 的数据结构来获得你想要的结果?就像在发布数据之前一样,data.items = data.items.item;? JSON 作为请求体传入,我不知道预先转换的方法。 一种方法是自定义反序列化器 是的,虽然不是很熟悉。我有 4 个这样的字段;如果我们可以用一些注释来做到这一点会更好吗? 可以在public class TrasactionDTObaeldung.com/jackson-annotations之上尝试这个@JsonRootName("items") 【参考方案1】:

您可以使用Map 来表示中间Items 对象。

鉴于此示例(所有字段 public 仅用于演示目的):

public class Item 
    public String itemNumber, itemDescription, itemPrice, itemQuantity, itemBrandName, itemCategory, itemTax;

...你可以通过两种方式实现你想要的:

1。通过使用构造函数:

public class TransactionDTO 
    private List<Item> items;

    @JsonCreator
    public TransactionDTO(@JsonProperty("items") final Map<String, List<Item>> items) 
        this.items = items.get("item");
    

2。通过使用 setter:

public class TransactionDTO 
    private List<Item> items;

    public void setItems(final Map<String, List<Item>> items) 
        this.items = items.get("item");
    

【讨论】:

【参考方案2】:

我们需要实现自定义反序列化器。因为我们想跳过一个内部字段,所以我们的实现应该:

    - 跳过开始对象 "any_field_name" - 跳过任何字段名称。我们假设我们只有一个内部字段。 [, ..., ] - 为 List 使用默认解串器。 - 跳过结束对象

使用上述概念实现应该很容易:

public class InnerListDeserializer extends JsonDeserializer<List> implements ContextualDeserializer 

    private final JavaType propertyType;

    public InnerListDeserializer() 
        this(null);
    

    public InnerListDeserializer(JavaType propertyType) 
        this.propertyType = propertyType;
    

    @Override
    public List deserialize(JsonParser p, DeserializationContext context) throws IOException 
        p.nextToken(); // SKIP START_OBJECT
        p.nextToken(); // SKIP any FIELD_NAME

        List list = context.readValue(p, propertyType);

        p.nextToken(); // SKIP END_OBJECT

        return list;
    

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) 
        return new InnerListDeserializer(property.getType());
    

假设我们有这样的JSON有效载荷:


  "transaction": 
    "items": 
      "item": [
        
          "itemNumber": "193487654",
          "itemDescription": "Widget",
          "itemPrice": "599.00",
          "itemQuantity": "1",
          "itemBrandName": "ACME",
          "itemCategory": "Electronics",
          "itemTax": "12.95"
        ,
        
          "itemNumber": "193487654",
          "itemDescription": "Widget",
          "itemPrice": "599.00",
          "itemQuantity": "1",
          "itemBrandName": "ACME",
          "itemCategory": "Electronics",
          "itemTax": "12.95"
        
      ]
    ,
    "name": "Pickle Rick"
  

JSON 之上我们可以映射到POJO 之下的类:

@JsonRootName("transaction")
public class Transaction 

    private String name;
    private List<Item> items;

    @JsonDeserialize(using = InnerListDeserializer.class)
    public List<Item> getItems() 
        return items;
    

    // getters, setters, toString


public class Item 

    private String itemNumber;

    // getters, setters, toString

为了证明它适用于许多不同的模型,让我们再介绍一个 JSON 有效负载:


  "product": 
    "products": 
      "innerArray": [
        
          "id": "1234"
        
      ]
    
  

还有另外两个POJO 课程:

@JsonRootName("product")
class Product 

    private List<ProductItem> products;

    @JsonDeserialize(using = InnerListDeserializer.class)
    public List<ProductItem> getProducts() 
        return products;
    

    // getters, setters, toString


class ProductItem 

    private String id;

    // getters, setters, toString

现在我们可以测试我们的解决方案了:

import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class JSoupTest 

    public static void main(String[] args) throws Exception 
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        File jsonFile = new File("Path to 1-st JSON").getAbsoluteFile();
        File jsonFile1 = new File("Path to 2-nd JSON").getAbsoluteFile();

        System.out.println(mapper.readValue(jsonFile, Transaction.class));
        System.out.println(mapper.readValue(jsonFile1, Product.class));
    

以上示例打印:

Transactionitems=[ItemitemNumber=193487654, ItemitemNumber=193487654], name='Pickle Rick'
Productproducts=[ProductItemid='1234']

更多信息请阅读:

    Custom Jackson Deserializer Getting Access to Current Field Class Getting Started with Custom Deserialization in Jackson Jackson Exceptions – Problems and Solutions Jackson UNWRAP_ROOT_VALUE Configuring ObjectMapper in Spring

【讨论】:

【参考方案3】:

看来@JsonUnwrapped是我需要的。

https://www.baeldung.com/jackson-annotations

@JsonUnwrapped 定义了在序列化/反序列化时应该展开/展平的值。

让我们看看它到底是如何工作的;我们将使用注解来解开属性名称:

public class UnwrappedUser 
    public int id;
 
    @JsonUnwrapped
    public Name name;
 
    public static class Name 
        public String firstName;
        public String lastName;
    
 

现在让我们序列化这个类的一个实例:

@Test
public void whenSerializingUsingJsonUnwrapped_thenCorrect()
  throws JsonProcessingException, ParseException 
    UnwrappedUser.Name name = new UnwrappedUser.Name("John", "Doe");
    UnwrappedUser user = new UnwrappedUser(1, name);
 
    String result = new ObjectMapper().writeValueAsString(user);
     
    assertThat(result, containsString("John"));
    assertThat(result, not(containsString("name")));

以下是输出的样子——静态嵌套类的字段与其他字段一起展开:


    "id":1,
    "firstName":"John",
    "lastName":"Doe"

所以,它应该是这样的:

public class TrasactionDTO 
    
    private List<Item> items;
    ...


public static class Item 
    @JsonUnwrapped
    private InnerItem innerItem;
    ...



public static class InnerItem 
    private String itemNumber;
    ...

【讨论】:

以上是关于Jackson - 将内部对象列表反序列化为更高级别的列表的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Jackson 将原始 JSON 反序列化为 Java 对象

使用 Jackson 反序列化重复键以列出

Jackson xml反序列化 - 序列化为一个列表,其中包含任意元素

使用Jackson将JSON数组反序列化为单个Java对象

Jackson 将额外字段反序列化为 Map

使用 Jackson 将文件类型从 Angular 反序列化为 Java