使用 Jackson ObjectMapper 反序列化 JSON 对象双向一对多
Deserializing JSON object bi-directional one-to-many with Jackson ObjectMapper
我正在尝试反序列化 JSON 对象,例如
{
"name":"aaa",
"children": [
{"name":"bbb"}
]
}
进入 Java 个子对象引用父对象的对象,例如:
public class Parent {
public String name;
public List<Child> children;
}
public class Child {
public String name;
public Parent parent;
}
// ...
new ObjectMapper().readValue(<JSON>, Parent.class);
像这样反序列化时,Child#parent
不会指向父对象。
我在进行在线研究时阅读了两种方法,但似乎都没有用。
1.向 Child
class 添加构造函数参数以设置父对象
public class Child {
public String name;
public Parent parent;
public Child(Parent parent) {
this.parent = parent;
}
}
执行此操作时出现错误:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `Child` (no Creators, like default construct, exist):
cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"name":"aaa","children":[{"name":"bbb"}]}"; line: 1, column: 27]
(through reference chain: Parent["children"]->java.util.ArrayList[0])
2。使用 @JsonBackReference
和 @JsonManagedReference
注释
public class Parent {
public String name;
@JsonBackReference
public List<Child> children;
}
public class Child {
public String name;
@JsonManagedReference
public Parent parent;
}
这失败了:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot handle managed/back reference 'defaultReference':
back reference type (java.util.List) not compatible with managed type (Child)
at [Source: (String)"{"name":"aaa","children":[{"name":"bbb"}]}"; line: 1, column: 1]
@JsonBackReference
的Java文档说不能应用于集合,所以显然是行不通的,但是我想知道为什么网上有那么多应用于集合的例子。
问题
当对象图被反序列化时,如何实现子对象自动设置其 parent/owner 对象。实际上,我更愿意以某种方式让第一种方法以某种方式工作,因为它不会污染 require 以使用特定于框架的注释来污染 classes。
您可能需要为此编写自己的简单反序列化器。大概看起来像这样:
public class FamilyDeserializer extends JsonDeserializer<Parent> {
private ObjectMapper mapper;
@Override
public Parent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
Parent parent = mapper.readValue(p, new TypeReference<Parent>() {
});
Child child = mapper.readValue(node.get("children").toString(), new TypeReference<Child>() {
});
parent.setChild(child);
return parent;
}
我花了更多时间深入研究 Jackson 源代码我想出了一个使用自定义 BeanDeserializer
的通用解决方案。自定义反序列化器检查要序列化的 JSON 节点的相应 Java class 是否具有单参数构造函数,该构造函数可以获取父对象并使用它来实例化对象。
import org.apache.commons.lang3.reflect.ConstructorUtils;
public static class CustomBeanDeserializer extends BeanDeserializer {
private static final long serialVersionUID = 1L;
public CustomBeanDeserializer(BeanDeserializerBase src) {
super(src);
}
@Override
protected Object deserializeFromObjectUsingNonDefault(JsonParser p, DeserializationContext ctxt) throws IOException {
Object parentObject = getParentObject(p);
if (parentObject != null) {
// determine constructor that takes parent object
Constructor<?> ctor = ConstructorUtils.getMatchingAccessibleConstructor(_beanType.getRawClass(), parentObject.getClass());
if (ctor != null) {
try {
// instantiate object
Object bean = ctor.newInstance(parentObject);
p.setCurrentValue(bean);
// deserialize fields
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop == null) {
handleUnknownVanilla(p, ctxt, bean, propName);
continue;
}
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (final Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
} while ((propName = p.nextFieldName()) != null);
}
return bean;
} catch (ReflectiveOperationException ex) {
ex.printStackTrace();
}
}
}
return super.deserializeFromObjectUsingNonDefault(p, ctxt);
}
private Object getParentObject(JsonParser p) {
JsonStreamContext parentCtx = p.getParsingContext().getParent();
if (parentCtx == null)
return null;
Object parentObject = parentCtx.getCurrentValue();
if (parentObject == null)
return null;
if (parentObject instanceof Collection || parentObject instanceof Map || parentObject.getClass().isArray()) {
parentCtx = parentCtx.getParent();
if (parentCtx != null) {
parentObject = parentCtx.getCurrentValue();
}
}
return parentObject;
}
}
解串器可以这样使用:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule myModule = new SimpleModule();
myModule.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig cfg, BeanDescription beanDescr, JsonDeserializer<?> deserializer) {
if (deserializer instanceof BeanDeserializerBase)
return new CustomBeanDeserializer((BeanDeserializerBase) deserializer);
return deserializer;
}
});
objectMapper.registerModule(myModule);
objectMapper.readValue(<JSON>, Parent.class);
我仍然对需要更少自定义代码的更好解决方案感兴趣。
关于您的第二种方法,您必须更改注释的一侧。
public class Parent {
public String name;
@JsonManagedReference
public List<Child> children;
}
public class Child {
public String name;
@JsonBackReference
public Parent parent;
}
我正在尝试反序列化 JSON 对象,例如
{
"name":"aaa",
"children": [
{"name":"bbb"}
]
}
进入 Java 个子对象引用父对象的对象,例如:
public class Parent {
public String name;
public List<Child> children;
}
public class Child {
public String name;
public Parent parent;
}
// ...
new ObjectMapper().readValue(<JSON>, Parent.class);
像这样反序列化时,Child#parent
不会指向父对象。
我在进行在线研究时阅读了两种方法,但似乎都没有用。
1.向 Child
class 添加构造函数参数以设置父对象
public class Child {
public String name;
public Parent parent;
public Child(Parent parent) {
this.parent = parent;
}
}
执行此操作时出现错误:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `Child` (no Creators, like default construct, exist):
cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"name":"aaa","children":[{"name":"bbb"}]}"; line: 1, column: 27]
(through reference chain: Parent["children"]->java.util.ArrayList[0])
2。使用 @JsonBackReference
和 @JsonManagedReference
注释
public class Parent {
public String name;
@JsonBackReference
public List<Child> children;
}
public class Child {
public String name;
@JsonManagedReference
public Parent parent;
}
这失败了:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot handle managed/back reference 'defaultReference':
back reference type (java.util.List) not compatible with managed type (Child)
at [Source: (String)"{"name":"aaa","children":[{"name":"bbb"}]}"; line: 1, column: 1]
@JsonBackReference
的Java文档说不能应用于集合,所以显然是行不通的,但是我想知道为什么网上有那么多应用于集合的例子。
问题 当对象图被反序列化时,如何实现子对象自动设置其 parent/owner 对象。实际上,我更愿意以某种方式让第一种方法以某种方式工作,因为它不会污染 require 以使用特定于框架的注释来污染 classes。
您可能需要为此编写自己的简单反序列化器。大概看起来像这样:
public class FamilyDeserializer extends JsonDeserializer<Parent> {
private ObjectMapper mapper;
@Override
public Parent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
Parent parent = mapper.readValue(p, new TypeReference<Parent>() {
});
Child child = mapper.readValue(node.get("children").toString(), new TypeReference<Child>() {
});
parent.setChild(child);
return parent;
}
我花了更多时间深入研究 Jackson 源代码我想出了一个使用自定义 BeanDeserializer
的通用解决方案。自定义反序列化器检查要序列化的 JSON 节点的相应 Java class 是否具有单参数构造函数,该构造函数可以获取父对象并使用它来实例化对象。
import org.apache.commons.lang3.reflect.ConstructorUtils;
public static class CustomBeanDeserializer extends BeanDeserializer {
private static final long serialVersionUID = 1L;
public CustomBeanDeserializer(BeanDeserializerBase src) {
super(src);
}
@Override
protected Object deserializeFromObjectUsingNonDefault(JsonParser p, DeserializationContext ctxt) throws IOException {
Object parentObject = getParentObject(p);
if (parentObject != null) {
// determine constructor that takes parent object
Constructor<?> ctor = ConstructorUtils.getMatchingAccessibleConstructor(_beanType.getRawClass(), parentObject.getClass());
if (ctor != null) {
try {
// instantiate object
Object bean = ctor.newInstance(parentObject);
p.setCurrentValue(bean);
// deserialize fields
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop == null) {
handleUnknownVanilla(p, ctxt, bean, propName);
continue;
}
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (final Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
} while ((propName = p.nextFieldName()) != null);
}
return bean;
} catch (ReflectiveOperationException ex) {
ex.printStackTrace();
}
}
}
return super.deserializeFromObjectUsingNonDefault(p, ctxt);
}
private Object getParentObject(JsonParser p) {
JsonStreamContext parentCtx = p.getParsingContext().getParent();
if (parentCtx == null)
return null;
Object parentObject = parentCtx.getCurrentValue();
if (parentObject == null)
return null;
if (parentObject instanceof Collection || parentObject instanceof Map || parentObject.getClass().isArray()) {
parentCtx = parentCtx.getParent();
if (parentCtx != null) {
parentObject = parentCtx.getCurrentValue();
}
}
return parentObject;
}
}
解串器可以这样使用:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule myModule = new SimpleModule();
myModule.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig cfg, BeanDescription beanDescr, JsonDeserializer<?> deserializer) {
if (deserializer instanceof BeanDeserializerBase)
return new CustomBeanDeserializer((BeanDeserializerBase) deserializer);
return deserializer;
}
});
objectMapper.registerModule(myModule);
objectMapper.readValue(<JSON>, Parent.class);
我仍然对需要更少自定义代码的更好解决方案感兴趣。
关于您的第二种方法,您必须更改注释的一侧。
public class Parent {
public String name;
@JsonManagedReference
public List<Child> children;
}
public class Child {
public String name;
@JsonBackReference
public Parent parent;
}