Jackson - 如何处理(反序列化)嵌套的 JSON?

Posted

技术标签:

【中文标题】Jackson - 如何处理(反序列化)嵌套的 JSON?【英文标题】:Jackson - How to process (deserialize) nested JSON? 【发布时间】:2012-07-29 15:33:15 【问题描述】:

  vendors: [
    
      vendor: 
        id: 367,
        name: "Kuhn-Pollich",
        company_id: 1,
      
    ,
    
      vendor: 
        id: 374,
        name: "Sawayn-Hermann",
        company_id: 1,
      
  ]

我有一个可以从单个“供应商”json 正确反序列化的供应商对象,但我想将其反序列化为 Vendor[],我只是不知道如何让杰克逊合作。有什么建议吗?

【问题讨论】:

这是无效的 JSON。 vendors 有一个数组作为值,它有一个对象,并且单个对象有一个“供应商”属性,后面是一个裸露的***对象。即第二个vendor 对象在数组中的单个对象中没有关联的属性。此外,属性名称不是字符串,它们需要在 JSON 中引用。我猜你输入错误的JSON?一个好的答案取决于您实际使用的是哪种 JSOn。 抱歉,让我更正 JSON -- 现在应该修复 您不能(或不想)拥有一个包含 List 的 Vendors 类? 我是,但问题是 Vendor 对象嵌套为数组中每个对象的“vendor”属性,而不是对象本身。这意味着我必须有一个带有 VendorWrapper 列表的 Vendors 类,其中每个 VendorWrapper 都包含一个 Vendor。我现在有了这个设置,但不太理想。 【参考方案1】:

这是一个粗略但更具声明性的解决方案。我无法将其归结为单个注释,但这似乎运作良好。也不确定大型数据集的性能。

鉴于此 JSON:


    "list": [
        
            "wrapper": 
                "name": "Jack"
            
        ,
        
            "wrapper": 
                "name": "Jane"
            
        
    ]

还有这些模型对象:

public class RootObject 
    @JsonProperty("list")
    @JsonDeserialize(contentUsing = SkipWrapperObjectDeserializer.class)
    @SkipWrapperObject("wrapper")
    public InnerObject[] innerObjects;

public class InnerObject 
    @JsonProperty("name")
    public String name;

Jackson voodoo 的实现方式如下:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface SkipWrapperObject 
    String value();

public class SkipWrapperObjectDeserializer extends JsonDeserializer<Object> implements
        ContextualDeserializer 
    private Class<?> wrappedType;
    private String wrapperKey;

    public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
            BeanProperty property) throws JsonMappingException 
        SkipWrapperObject skipWrapperObject = property
                .getAnnotation(SkipWrapperObject.class);
        wrapperKey = skipWrapperObject.value();
        JavaType collectionType = property.getType();
        JavaType collectedType = collectionType.containedType(0);
        wrappedType = collectedType.getRawClass();
        return this;
    

    @Override
    public Object deserialize(JsonParser parser, DeserializationContext ctxt)
            throws IOException, JsonProcessingException 
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode objectNode = mapper.readTree(parser);
        JsonNode wrapped = objectNode.get(wrapperKey);
        Object mapped = mapIntoObject(wrapped);
        return mapped;
    

    private Object mapIntoObject(JsonNode node) throws IOException,
            JsonProcessingException 
        JsonParser parser = node.traverse();
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(parser, wrappedType);
    

希望这对某人有用!

【讨论】:

+1 for mapIntoObject(JsonNode)... 所以如果你想使用 readValueJsonNode 只需传递它的 traverse() 效果很好:我建议的一个小简化是ObjectMapper.convertValue(),它可以用来替换mapIntoObject中的3行:return mapper.convertValue(node, wrappedType); @Patrick,我尝试运行此示例代码。在 WrapperType =collectedType.getRawClass() 处获取 SkipWrapperObjectDeserializer 中的 NPE;在 createContextual() 方法中。你能帮我解决这个问题吗?谢谢。【参考方案2】:

您的数据存在问题,因为您的数组中有内部 wrapper 对象。大概您的 Vendor 对象旨在处理 idnamecompany_id,但这些多个对象中的每一个也都包含在具有单个属性 vendor 的对象中。

我假设您使用的是 Jackson Data Binding 模型。

如果是这样,那么需要考虑两件事:

第一个是使用特殊的 Jackson 配置属性。 Jackson - 我相信从 1.9 开始,如果您使用的是旧版本的 Jackson,这可能不可用 - 提供 UNWRAP_ROOT_VALUE。它专为将结果包装在要丢弃的***单一属性对象中的情况而设计。

所以,玩弄:

objectMapper.configure(SerializationConfig.Feature.UNWRAP_ROOT_VALUE, true);

第二个是使用包装对象。即使在丢弃外部包装对象之后,您仍然会遇到 Vendor 对象被包装在单一属性对象中的问题。使用包装器来解决这个问题:

class VendorWrapper

    Vendor vendor;

    // gettors, settors for vendor if you need them

同样,您也可以定义一个包装类来处理外部对象,而不是使用UNWRAP_ROOT_VALUES。假设您有正确的VendorVendorWrapper 对象,您可以定义:

class VendorsWrapper

    List<VendorWrapper> vendors = new ArrayList<VendorWrapper>();

    // gettors, settors for vendors if you need them


// in your deserialization code:
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readValue(jsonInput, VendorsWrapper.class); 

VendorsWrapper 的对象树类似于您的 JSON:

VendorsWrapper:
    vendors:
    [
        VendorWrapper
            vendor: Vendor,
        VendorWrapper:
            vendor: Vendor,
        ...
    ]

最后,您可以使用 Jackson Tree Model 将其解析为 JsonNodes,丢弃外部节点,并为 ArrayNode 中的每个 JsonNode 调用:

mapper.readValue(node.get("vendor").getTextValue(), Vendor.class);

这可能会导致更少的代码,但看起来并不比使用两个包装器更笨拙。

【讨论】:

谢谢,我就是这样做的,只是希望有更好的方法。我会把它标记为正确的。 我还希望看到使用 UNWRAP_ROOT_VALUES 之类的解决方案,仅深 1 级,但我认为这是不可能的。当然,另一种选择是使用自定义反序列化器,只需添加挂钩来查找您感兴趣的实际对象并丢弃其他所有内容,或者使用 Jackson Tree Model 方法,丢弃***JsonNode,并获取包装您的供应商的JsonNodes,调用getTextValue,并将那个传递给mapper.readValue。杰克逊真的给了你很多选择。【参考方案3】:

@帕特里克 我会稍微改进一下你的解决方案

@Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException         
    ObjectNode objectNode = jp.readValueAsTree();
    JsonNode wrapped = objectNode.get(wrapperKey);
    JsonParser parser = node.traverse();
    parser.setCodec(jp.getCodec());
    Vendor mapped = parser.readValueAs(Vendor.class);
    return mapped;

它工作得更快:)

【讨论】:

【参考方案4】:

我迟到了,但一种方法是使用静态内部类来解包值:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

class Scratch 
    private final String aString;
    private final String bString;
    private final String cString;
    private final static String jsonString;

    static 
        jsonString = "\n" +
                "  \"wrap\" : \n" +
                "    \"A\": \"foo\",\n" +
                "    \"B\": \"bar\",\n" +
                "    \"C\": \"baz\"\n" +
                "  \n" +
                "";
    

    @JsonCreator
    Scratch(@JsonProperty("A") String aString,
            @JsonProperty("B") String bString,
            @JsonProperty("C") String cString) 
        this.aString = aString;
        this.bString = bString;
        this.cString = cString;
    

    @Override
    public String toString() 
        return "Scratch" +
                "aString='" + aString + '\'' +
                ", bString='" + bString + '\'' +
                ", cString='" + cString + '\'' +
                '';
    

    public static class JsonDeserializer 
        private final Scratch scratch;

        @JsonCreator
        public JsonDeserializer(@JsonProperty("wrap") Scratch scratch) 
            this.scratch = scratch;
        

        public Scratch getScratch() 
            return scratch;
        
    

    public static void main(String[] args) throws JsonProcessingException 
        ObjectMapper objectMapper = new ObjectMapper();
        Scratch scratch = objectMapper.readValue(jsonString, Scratch.JsonDeserializer.class).getScratch();
        System.out.println(scratch.toString());
    

但是,将objectMapper.configure(SerializationConfig.Feature.UNWRAP_ROOT_VALUE, true);@JsonRootName("aName")、as pointed out by pb2q 结合使用可能更容易

【讨论】:

以上是关于Jackson - 如何处理(反序列化)嵌套的 JSON?的主要内容,如果未能解决你的问题,请参考以下文章

spring boot中各种数据不匹配如何处理jackson反序列化错误

OpenMP 如何处理嵌套循环?

Python继承模式 - 如何处理反序列化

使用Jackson在android中反序列化具有相同键但不同类型的json

如何处理反序列化变化的 JSON 数据?

如何处理 API 有时将属性作为数组返回,有时作为对象返回?