Java Jackson 基于类型反序列化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/44122782/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Hymanson deserialize based on type
提问by John Baum
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. The "type" field dictates the "object" deserialization ie. type = foo means the deserialize the "object" field using the Foo.java. (if type = Bar, use Bar.java to deserialize the object field). Metadata/owner will always deserialize the same way using a simple Hymanson annotated Java class for each. Is there a way to accomplish this using annotations? If not, how can this be done using a custom deserializer?
我试图避免自定义反序列化器并尝试将上述 JSON(称为 Wrapper.java)反序列化为 Java POJO。“类型”字段指示“对象”反序列化,即。type = foo 表示使用 Foo.java 反序列化“对象”字段。(如果 type = Bar,则使用 Bar.java 反序列化对象字段)。元数据/所有者将始终使用一个简单的 Hymanson 注释的 Java 类为每个对象反序列化相同的方式。有没有办法使用注释来实现这一点?如果没有,如何使用自定义解串器完成此操作?
采纳答案by cassiomolin
Annotations-only approach
仅注释方法
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 typeas 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, @JsonTypeInfois set to use typeas an external propertyto determine the right class to map the dataproperty.
在这种方法中,@JsonTypeInfo被设置为type用作外部属性来确定映射该data属性的正确类。
The JSON document can be deserialized as following:
JSON 文档可以反序列化如下:
ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);
回答by Renats Stozkovs
I think it is rather straight-forward. You probably have a super class that has properties for metadataand owner, so rather than making it truly generic, you could substitute T for your super class. 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:
我认为这是相当直接的。您可能有一个具有metadataand属性的超类owner,因此与其让它真正泛型,您还可以用 T 代替您的超类。但基本上,您必须从实际的 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;
}
回答by cassiomolin
Custom deserializer approach
自定义解串器方法
You could use a custom deserializer that checks the typeproperty to parse the objectproperty into the most suitable class.
您可以使用自定义反序列化器来检查type属性以将object属性解析为最合适的类。
First define an interface that will be implemented by Fooand Barclasses:
首先定义一个将由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 Wrapperand Dataclasses:
然后定义你的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 objectfield is annotated with @JsonDeserialize, indicating the deserializer that will be used for the objectproperty.
该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 Hymanson'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.
回答by Spunc
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. This class needs to be annotated with @JsonTypeInfo. It should look like:
使用“元数据”和“所有者”等公共字段及其 getter/setter 创建一个抽象超类。此类需要使用@JsonTypeInfo进行注释。它应该看起来像:
@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 typein your JSON document.
使用参数property = "type"指定类标识符将在 JSON 文档中的字段类型下序列化。
The value of the class identifier can be specified with use. Id.CLASSuses the fully-qualified Java class name. You can also use Id.MINIMAL_CLASSwhich is an abbreviated Java class name. To have your own identifier, use Id.NAME. In this case, you need to declare the subtypes:
类标识符的值可以用 指定use。Id.CLASS使用完全限定的 Java 类名。您还可以使用Id.MINIMAL_CLASSwhich 是一个缩写的 Java 类名。要拥有自己的标识符,请使用Id.NAME. 在这种情况下,您需要声明子类型:
@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。
Hymanson's ObjectMapper will use the additional field "type" of the JSON document for serialization and deserialization. E. g. when you deserialise a JSON string into a super class reference, it will be of the appropriate subclass:
Hymanson 的 ObjectMapper 将使用 JSON 文档的附加字段“type”进行序列化和反序列化。例如 当您将 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.Hymanson.annotation.JsonTypeInfo;
import com.fasterxml.Hymanson.annotation.JsonTypeInfo.Id;
import com.fasterxml.Hymanson.databind.ObjectMapper;
import com.fasterxml.Hymanson.annotation.JsonTypeInfo.As;
import java.io.IOException;
import com.fasterxml.Hymanson.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 + ']';
}
}

