【问题标题】:How do I unwrap a list of list wrapped items in Jackson?如何解开 Jackson 中的列表包装项目列表?
【发布时间】:2019-12-07 18:24:36
【问题描述】:

我有一个类似这样的 bean:

public class Product {

    public String id;
    public String vendor;
    public Set<Image> images;
}

public class Image {

    public String originalSrc;
}

我正在尝试反序列化类似于以下内容的 JSON:

{
  "id": "gid:\/\/mysite\/Product\/1853361520730",
  "vendor": "gadgetdown",
  "images": {
    "edges": [
      {
        "node": {
          "originalSrc": "https:\/\/cdn.something.com"
        }
      },
      {
        "node": {
          "originalSrc": "https:\/\/cdn.something.com"
        }
      }
    ]
  }

我无法反序列化该对象,因为每个 image 对象都包装在 node 对象中,并共同包含在 edges 对象中。


编辑:为清楚起见,我不想通过使用 bean 来完成此操作,此示例是一种简化,JSON 有效负载中的所有数组项都包含在此 edgesnode 表示中。

【问题讨论】:

标签: java json jackson jackson-databind


【解决方案1】:

要从"node": { "originalSrc": "https:\/\/cdn.something.com" } 中解开Images,您只需使用@JsonRootName 注释

@JsonRootName(value = "node")
class Image {
    public String originalSrc;
}

但是从"images": { "edges": [{...}, {...}] } 中解包图像集合有点复杂,需要使用自定义JsonDeserializer

class Product {
    public String id;
    public String vendor;

    @JsonDeserialize(using = ImageSetDeserializer.class)
    public Set<Image> images;
}


class ImageSetDeserializer extends JsonDeserializer<Set<Image>> {
    public Set<Image> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
        JsonNode node = mapper.readTree(jsonParser);
        return mapper.convertValue(node.get("edges").findValues("node"), new TypeReference<Set<Image>>() {});
    }
}

终于测试了:

public class ProductTest {

    private final String source = "{\n" +
            "  \"id\": \"gid:\\/\\/mysite\\/Product\\/1853361520730\",\n" +
            "  \"vendor\": \"gadgetdown\",\n" +
            "  \"images\": {\n" +
            "    \"edges\": [\n" +
            "      {\n" +
            "        \"node\": {\n" +
            "          \"originalSrc\": \"https:\\/\\/cdn.something.com\"\n" +
            "        }\n" +
            "      },\n" +
            "      {\n" +
            "        \"node\": {\n" +
            "          \"originalSrc\": \"https:\\/\\/cdn.something.com\"\n" +
            "        }\n" +
            "      }\n" +
            "    ]\n" +
            "  }" +
            "}";

    @Test
    public void test() throws IOException {
        ObjectMapper mapper = new ObjectMapper();

        Product product = mapper.readValue(source, Product.class);

        assertEquals(product.id, "gid://mysite/Product/1853361520730");
        assertEquals(product.vendor, "gadgetdown");
        assertNotNull(product.images);
        List<Image> images = new ArrayList<>(product.images);
        assertEquals(images.size(), 2);
        assertEquals(images.get(0).originalSrc, "https://cdn.something.com");
        assertEquals(images.get(1).originalSrc, "https://cdn.something.com");
    }
}

【讨论】:

    【解决方案2】:

    如果每个列表都有如下结构:

    {
      "images": {
        "edges": [
          {
            "node": {
              "entry": "entry-value"
            }
          }
        ]
      }
    }
    

    每个列表都是带有edges 属性的JSON Object,数组中的每个元素都由带有node 属性的JSON Object 包装。对于这种结构,我们可以编写类似于Jackson - deserialize inner list of objects to list of one higher level question 中的通用反序列化器。

    例如Set解串器:

    class InnerSetDeserializer extends JsonDeserializer<Set> implements ContextualDeserializer {
    
      private final JavaType propertyType;
    
      public InnerSetDeserializer() {
        this(null);
      }
    
      public InnerSetDeserializer(JavaType propertyType) {
        this.propertyType = propertyType;
      }
    
      @Override
      public Set deserialize(JsonParser p, DeserializationContext context) throws IOException {
        p.nextToken(); // SKIP START_OBJECT
        p.nextToken(); // SKIP any FIELD_NAME
    
        CollectionType collectionType = getCollectionType(context);
        List<Map<String, Object>> list = context.readValue(p, collectionType);
    
        p.nextToken(); // SKIP END_OBJECT
    
        return list.stream().map(Map::values).flatMap(Collection::stream).collect(Collectors.toSet());
      }
    
      private CollectionType getCollectionType(DeserializationContext context) {
        TypeFactory typeFactory = context.getTypeFactory();
        MapType mapType =
            typeFactory.constructMapType(
                Map.class, String.class, propertyType.getContentType().getRawClass());
    
        return typeFactory.constructCollectionType(List.class, mapType);
      }
    
      @Override
      public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) {
        return new InnerSetDeserializer(property.getType());
      }
    }
    

    我们可以使用如下:

    class Product {
    
      private String id;
      private String vendor;
    
      @JsonDeserialize(using = InnerSetDeserializer.class)
      private Set<Image> images;
    
      // getters, setters
    }
    

    示例应用:

    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.databind.BeanProperty;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
    import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
    import com.fasterxml.jackson.databind.type.CollectionType;
    import com.fasterxml.jackson.databind.type.MapType;
    import com.fasterxml.jackson.databind.type.TypeFactory;
    import java.io.File;
    import java.io.IOException;
    import java.util.Collection;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    public class JsonApp {
    
      public static void main(String[] args) throws IOException {
        File jsonFile = new File("./resources/test.json");
    
        ObjectMapper mapper = new ObjectMapper();
        Product product = mapper.readValue(jsonFile, Product.class);
        System.out.println(product);
      }
    }
    

    上面的代码打印:

    Product{id='gid://mysite/Product/1853361520730', vendor='gadgetdown', images=[Image{originalSrc='https://cdn.something.com'}, Image{originalSrc='https://cdn.something.com'}]}
    

    【讨论】:

      【解决方案3】:

      所以images 不是set,而是JSONObject,其中包含edges 列表

      public class Images {
      
         private List<Edge> edges;
      
          }
      

      每个Edge 都包含Node 对象,

      public class Edge {
      
         private Node node;
      
         }
      

      每个Node 都有一个字符串属性originalSrc

      public class Node  {
      
       private String originalSrc;
      
        }
      

      【讨论】:

      • 很抱歉我不够详细。请看我的编辑。谢谢。
      • 所以你不想要java bean,那么你想怎么做呢?通过使用JSONObjectJSONArray? @MridangAgarwalla
      猜你喜欢
      • 2022-10-17
      • 2015-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-18
      • 1970-01-01
      • 2013-01-15
      相关资源
      最近更新 更多