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 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 propertyto 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);
回答by Renats Stozkovs
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. 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:
我认为这是相当直接的。您可能有一个具有metadata
and属性的超类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 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 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.CLASS
uses the fully-qualified Java class name. You can also use Id.MINIMAL_CLASS
which 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_CLASS
which 是一个缩写的 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 + ']';
}
}