简体   繁体   English

杰克逊根据类型反序列化

[英]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? 如果没有,如何使用自定义解串器完成?

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 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);  

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: 首先定义一个将由FooBar类实现的接口:

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: 然后定义您的WrapperData类:

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. 你可能有一个拥有metadataowner属性的超类,所以你可以用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM