次の形式の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クラスを使用して、常に同じ方法でデシリアライズします。注釈を使用してこれを達成する方法はありますか?そうでない場合、カスタムデシリアライザーを使用してこれをどのように行うことができますか?
カスタムデシリアライザーアプローチ の代わりに、注釈のみのソリューション( 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);
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
フィールドには @JsonDeserialize
、object
プロパティに使用されるデシリアライザーを示します。
デシリアライザーは次のように定義されます。
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);
このカスタムデシリアライザーの代わりに、 注釈のみのアプローチ を検討してください。
これはすべて、注釈を使用して実行できます。
「メタデータ」や「所有者」などの共通フィールドとそれらのゲッター/セッターを持つ抽象スーパークラスを作成します。このクラスには @ 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 + ']';
}
}
かなり簡単だと思います。おそらくmetadata
と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
全体的なコードは次のようになります。
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;
}