[英]Jackson deserialize based on type
Lets say I have JSON of the following format: 假设我有以下格式的JSON:
{
"type" : "Foo"
"data" : {
"object" : {
"id" : "1"
"fizz" : "bizz"
...
},
"metadata" : {
...
},
"owner" : {
"name" : "John"
...
}
}
}
I am trying to avoid custom deserializer and attempting to deserialize the above JSON (called Wrapper.java) into Java POJOs. 我试图避免自定义反序列化器并尝试将上述JSON(称为Wrapper.java)反序列化为Java POJO。 The "type" field dictates the "object" deserialization ie. “类型”字段指示“对象”反序列化即。 type = foo means the deserialize the "object" field using the Foo.java. type = foo表示使用Foo.java反序列化“object”字段。 (if type = Bar, use Bar.java to deserialize the object field). (如果type = Bar,请使用Bar.java反序列化对象字段)。 Metadata/owner will always deserialize the same way using a simple Jackson annotated Java class for each. 元数据/所有者将始终使用简单的Jackson注释Java类以相同的方式反序列化。 Is there a way to accomplish this using annotations? 有没有办法使用注释来完成这个? If not, how can this be done using a custom deserializer? 如果没有,如何使用自定义解串器完成?
Alternatively to the custom deserializer approach , you can have the following for an annotations-only solution (similar to the one described in Spunc's answer , but using type
as an external property ): 作为自定义反序列化器方法的替代方法 ,您可以使用以下注释解决方案(类似于Spunc的答案中描述的解决方案 ,但使用type
作为外部属性 ):
public abstract class AbstractData {
private Owner owner;
private Metadata metadata;
// Getters and setters
}
public static final class FooData extends AbstractData {
private Foo object;
// Getters and setters
}
public static final class BarData extends AbstractData {
private Bar object;
// Getters and setters
}
public class Wrapper {
private String type;
@JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = FooData.class, name = "Foo"),
@JsonSubTypes.Type(value = BarData.class, name = "Bar")
})
private AbstractData data;
// Getters and setters
}
In this approach, @JsonTypeInfo
is set to use type
as an external property to determine the right class to map the data
property. 在此方法中, @JsonTypeInfo
设置为使用type
作为外部属性来确定映射data
属性的正确类。
The JSON document can be deserialized as following: JSON文档可以反序列化如下:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);
You could use a custom deserializer that checks the type
property to parse the object
property into the most suitable class. 您可以使用自定义反序列化器来检查type
属性,以将object
属性解析为最合适的类。
First define an interface that will be implemented by Foo
and Bar
classes: 首先定义一个将由Foo
和Bar
类实现的接口:
public interface Model {
}
public class Foo implements Model {
// Fields, getters and setters
}
public class Bar implements Model {
// Fields, getters and setters
}
Then define your Wrapper
and Data
classes: 然后定义您的Wrapper
和Data
类:
public class Wrapper {
private String type;
private Data data;
// Getters and setters
}
public class Data {
@JsonDeserialize(using = ModelDeserializer.class)
private Model object;
private Metadata metadata;
private Owner owner;
// Getters and setters
}
The object
field is annotated with @JsonDeserialize
, indicating the deserializer that will be used for the object
property. object
字段使用@JsonDeserialize
注释,指示将用于object
属性的反序列化器。
The deserializer is defined as following: 解串器定义如下:
public class ModelDeserializer extends JsonDeserializer<Model> {
@Override
public Model deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonMappingException {
// Get reference to ObjectCodec
ObjectCodec codec = jp.getCodec();
// Parse "object" node into Jackson's tree model
JsonNode node = codec.readTree(jp);
// Get value of the "type" property
String type = ((Wrapper) jp.getParsingContext().getParent()
.getCurrentValue()).getType();
// Check the "type" property and map "object" to the suitable class
switch (type) {
case "Foo":
return codec.treeToValue(node, Foo.class);
case "Bar":
return codec.treeToValue(node, Bar.class);
default:
throw new JsonMappingException(jp,
"Invalid value for the \"type\" property");
}
}
}
The JSON document can be deserialized as following: JSON文档可以反序列化如下:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);
Alternatively to this custom deserializer, consider an annotations-only approach . 作为此自定义反序列化器的替代方法 ,请考虑仅注释方法 。
All this can be done by means of annotations. 所有这些都可以通过注释来完成。
Create an abstract superclass with the common fields like "metadata" and "owner" and their getters/setters. 使用“元数据”和“所有者”等公共字段及其getter / setter创建一个抽象超类。 This class needs to be annotated with @JsonTypeInfo . 该类需要使用@JsonTypeInfo进行注释。 It should look like: 它应该看起来像:
@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "type")
With the parameter property = "type"
you specify that the class identifier will be serialized under the field type in your JSON document. 使用参数property = "type"
您可以指定类标识符将在JSON文档中的字段类型下序列化。
The value of the class identifier can be specified with use
. 类标识符的值可以与指定use
。 Id.CLASS
uses the fully-qualified Java class name. Id.CLASS
使用完全限定的Java类名。 You can also use Id.MINIMAL_CLASS
which is an abbreviated Java class name. 您还可以使用Id.MINIMAL_CLASS
,它是缩写的Java类名。 To have your own identifier, use Id.NAME
. 要拥有自己的标识符,请使用Id.NAME
。 In this case, you need to declare the subtypes: 在这种情况下,您需要声明子类型:
@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Foo.class, name = "Foo"),
@JsonSubTypes.Type(value = Bar.class, name = "Bar")
})
Implement your classes Foo and Bar by extending from the abstract superclass. 通过从抽象超类扩展来实现您的类Foo和Bar。
Jackson's ObjectMapper will use the additional field "type" of the JSON document for serialization and deserialization. Jackson的ObjectMapper将使用JSON文档的附加字段“type”进行序列化和反序列化。 E. g. E. g。 when you deserialise a JSON string into a super class reference, it will be of the appropriate subclass: 当您将JSON字符串反序列化为超类引用时,它将是适当的子类:
ObjectMapper om = new ObjectMapper();
AbstractBase x = om.readValue(json, AbstractBase.class);
// x will be instanceof Foo or Bar
Complete code example (I used public fields as shortcut to not need to write getters/setters): 完整的代码示例 (我使用公共字段作为快捷方式,不需要编写getter / setter):
package test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonSubTypes;
@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Foo.class, name = "Foo"),
@JsonSubTypes.Type(value = Bar.class, name = "Bar")
})
public abstract class AbstractBase {
public MetaData metaData;
public Owner owner;
@Override
public String toString() {
return "metaData=" + metaData + "; owner=" + owner;
}
public static void main(String[] args) throws IOException {
// Common fields
Owner owner = new Owner();
owner.name = "Richard";
MetaData metaData = new MetaData();
metaData.data = "Some data";
// Foo
Foo foo = new Foo();
foo.owner = owner;
foo.metaData = metaData;
CustomObject customObject = new CustomObject();
customObject.id = 20l;
customObject.fizz = "Example";
Data data = new Data();
data.object = customObject;
foo.data = data;
System.out.println("Foo: " + foo);
// Bar
Bar bar = new Bar();
bar.owner = owner;
bar.metaData = metaData;
bar.data = "A String in Bar";
ObjectMapper om = new ObjectMapper();
// Test Foo:
String foojson = om.writeValueAsString(foo);
System.out.println(foojson);
AbstractBase fooDeserialised = om.readValue(foojson, AbstractBase.class);
System.out.println(fooDeserialised);
// Test Bar:
String barjson = om.writeValueAsString(bar);
System.out.println(barjson);
AbstractBase barDeserialised = om.readValue(barjson, AbstractBase.class);
System.out.println(barDeserialised);
}
}
class Foo extends AbstractBase {
public Data data;
@Override
public String toString() {
return "Foo[" + super.toString() + "; data=" + data + ']';
}
}
class Bar extends AbstractBase {
public String data;
public String toString() {
return "Bar[" + super.toString() + "; data=" + data + ']';
}
}
class Data {
public CustomObject object;
@Override
public String toString() {
return "Data[object=" + object + ']';
}
}
class CustomObject {
public long id;
public String fizz;
@Override
public String toString() {
return "CustomObject[id=" + id + "; fizz=" + fizz + ']';
}
}
class MetaData {
public String data;
@Override
public String toString() {
return "MetaData[data=" + data + ']';
}
}
class Owner {
public String name;
@Override
public String toString() {
return "Owner[name=" + name + ']';
}
}
I think it is rather straight-forward. 我认为这是相当直接的。 You probably have a super class that has properties for metadata
and owner
, so rather than making it truly generic, you could substitute T for your super class. 你可能有一个拥有metadata
和owner
属性的超类,所以你可以用T代替你的超类,而不是让它真正通用。 But basically, you will have to parse the name of the class from the actual JSON string, which in your example would look something like this: 但基本上,您必须从实际的JSON字符串中解析类的名称,在您的示例中,该字符串看起来像这样:
int start = jsonString.indexOf("type");
int end = jsonString.indexOf("data");
Class actualClass = Class.forName(jsonString.substring(start + 4, end - 2)); // that of course, is approximate - based on how you format JSON
and overall code could be something like this: 整体代码可能是这样的:
public static <T> T deserialize(String xml, Object obj)
throws JAXBException {
T result = null;
try {
int start = jsonString.indexOf("type");
int end = jsonString.indexOf("data");
Class actualClass = Class.forName(jsonString.substring(start + 4, end - 2));
JAXBContextFactory factory = JAXBContextFactory.getInstance();
JAXBContext jaxbContext = factory.getJaxBContext(actualClass);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
// this will create Java object
try (StringReader reader = new StringReader(xml)) {
result = (T) jaxbUnmarshaller.unmarshal(reader);
}
} catch (JAXBException e) {
log.error(String
.format("Exception while deserialising the object[JAXBException] %s\n\r%s",
e.getMessage()));
}
return result;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.