web-dev-qa-db-ja.com

ジャクソンは型に基づいてデシリアライズします

次の形式のJSONがあるとします。

{
    "type" : "Foo"
    "data" : {
        "object" : {
            "id" : "1"
            "fizz" : "bizz"
            ...
        },
        "metadata" : {
            ...
        },
        "owner" : {
            "name" : "John"
            ...
        }
    }
}

カスタムデシリアライザーを避け、上記のJSON(Wrapper.Javaと呼ばれる)をJava POJOにデシリアライズしようとしています。 「タイプ」フィールドは、「オブジェクト」デシリアライズを指示します。 type = fooは、Foo.Javaを使用して「オブジェクト」フィールドを逆シリアル化することを意味します。 (type = Barの場合、Bar.Javaを使用してオブジェクトフィールドを逆シリアル化します)。メタデータ/所有者は、それぞれに簡単なJackson注釈付きのJavaクラスを使用して、常に同じ方法でデシリアライズします。注釈を使用してこれを達成する方法はありますか?そうでない場合、カスタムデシリアライザーを使用してこれをどのように行うことができますか?

9
John Baum

注釈のみのアプローチ

カスタムデシリアライザーアプローチ の代わりに、注釈のみのソリューション( 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
}

このアプローチでは、 @JsonTypeInfo は、type外部プロパティ として使用して、dataプロパティをマップする適切なクラスを決定するように設定されます。

JSONドキュメントは、次のように逆シリアル化できます。

ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);  
15
cassiomolin

カスタムデシリアライザーアプローチ

typeプロパティをチェックして、objectプロパティを最適なクラスに解析するカスタムデシリアライザーを使用できます。

最初に、FooおよびBarクラスによって実装されるインターフェースを定義します。

public interface Model {

}
public class Foo implements Model {

    // Fields, getters and setters
}
public class Bar implements Model {

    // Fields, getters and setters
}

次に、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
}

objectフィールドには @JsonDeserializeobjectプロパティに使用されるデシリアライザーを示します。

デシリアライザーは次のように定義されます。

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

JSONドキュメントは、次のように逆シリアル化できます。

ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(json, Wrapper.class);  

このカスタムデシリアライザーの代わりに、 注釈のみのアプローチ を検討してください。

8
cassiomolin

これはすべて、注釈を使用して実行できます。

「メタデータ」や「所有者」などの共通フィールドとそれらのゲッター/セッターを持つ抽象スーパークラスを作成します。このクラスには @ JsonTypeInfo の注釈を付ける必要があります。次のようになります。

@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "type")

パラメーターproperty = "type"クラス識別子がJSONドキュメントのフィールドtypeでシリアル化されることを指定します。

クラス識別子の値は、useで指定できます。 Id.CLASSは、完全修飾のJavaクラス名を使用します。Id.MINIMAL_CLASS(省略形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")
})

抽象スーパークラスから拡張して、クラスFooおよびBarを実装します。

ジャクソンのObjectMapperは、シリアル化と逆シリアル化のためにJSONドキュメントの追加フィールド「タイプ」を使用します。例JSON文字列をスーパークラス参照に逆シリアル化すると、適切なサブクラスになります。

ObjectMapper om = new ObjectMapper();
AbstractBase x = om.readValue(json, AbstractBase.class);
// x will be instanceof Foo or Bar


完全なコード例(ゲッター/セッターを記述する必要がないように、公開フィールドをショートカットとして使用しました):

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 + ']';
    }
}
7
Spunc

かなり簡単だと思います。おそらくmetadataownerのプロパティを持つスーパークラスがあるので、それを真に汎用化するのではなく、スーパークラスの代わりに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

全体的なコードは次のようになります。

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;
}
2
Renats Stozkovs