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这是一个粗略但更具声明性的解决方案。我无法将其归结为单个注释,但这似乎运作良好。也不确定大型数据集的性能。
鉴于此 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 formapIntoObject(JsonNode)
... 所以如果你想使用 readValue
和 JsonNode
只需传递它的 traverse()
效果很好:我建议的一个小简化是ObjectMapper.convertValue()
,它可以用来替换mapIntoObject
中的3行:return mapper.convertValue(node, wrappedType);
@Patrick,我尝试运行此示例代码。在 WrapperType =collectedType.getRawClass() 处获取 SkipWrapperObjectDeserializer 中的 NPE;在 createContextual() 方法中。你能帮我解决这个问题吗?谢谢。【参考方案2】:
您的数据存在问题,因为您的数组中有内部 wrapper 对象。大概您的 Vendor
对象旨在处理 id
、name
、company_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
。假设您有正确的Vendor
、VendorWrapper
对象,您可以定义:
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反序列化错误