今、私はジャクソンと仕事をしていて、それについていくつか質問があります。
まず第一に。私には2つのサービスがあります。1つはデータ収集と送信サービスで、2つ目はこのデータを受信して、たとえばファイルに記録します。
したがって、最初のサービスには次のようなクラス階層があります。
+----ConcreteC
|
Base ----+----ConcreteA
|
+----ConcreteB
そして、2番目のサービスには次のようなクラス階層があります。
ConcreteAAdapter extends ConcreteA implements Adapter {}
ConcreteBAdapter extends ConcreteB implements Adapter {}
ConcreteCAdapter extends ConcreteC implements Adapter {}
最初のサービスはConcreteXAdapter
について何も知りません。
最初のサービスでデータを送信する方法:
Collection<Base> data = new LinkedBlockingQueue<Base>()
JacksonUtils utils = new JacksonUtils();
data.add(new ConcreteA());
data.add(new ConcreteB());
data.add(new ConcreteC());
...
send(utils.marshall(data));
...
public class JacksonUtils {
public byte[] marshall(Collection<Base> data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream() {
@Override
public byte[] toByteArray() {
return buf;
}
};
getObjectMapper().writeValue(out, data);
return out.toByteArray();
}
protected ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
public Object unmarshall(byte[] json) throws IOException {
return getObjectMapper().readValue(json, Object.class);
}
public <T> T unmarshall(InputStream source, TypeReference<T> typeReference) throws IOException {
return getObjectMapper().readValue(source, typeReference);
}
public <T> T unmarshall(byte[] json, TypeReference<T> typeReference) throws IOException {
return getObjectMapper().readValue(json, typeReference);
}
}
したがって、jsonをConcreteXAdapter
(ConcreteA -> ConcreteAAdapter, ConcreteB -> ConcreteBAdapter, ConcreteC -> ConcreteCAdapter
)のコレクションではなく、ConcreteX
のコレクションにデシリアライズしたいと思います。私が説明した場合、私は取得したいです:
Collection [ConcreteAAdapter, ConcreteBAdapter, ConcreteCAdapter]
これどうやってするの?
この問題の解決方法。以下は、サンプルプロジェクトのクラス図です。
したがって、逆シリアル化後にConcreteAAdapter
フォームConcreteA
を取得したいと思います。
私の解決策は、ClassNameIdResolver
を拡張して、基本クラスオブジェクトをサブタイプクラスオブジェクトに逆シリアル化する機能を追加することです(サブタイプクラスは追加の機能と追加のフィールドを追加しません)。
逆シリアル化のためにObjectMapper
を作成するコードを次に示します。
protected ObjectMapper getObjectMapperForDeserialization() {
ObjectMapper mapper = new ObjectMapper();
StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()) {
private HashMap<Class, Class> classes = new HashMap<Class, Class>() {
{
put(ConcreteA.class, ConcreteAAdapter.class);
put(ConcreteB.class, ConcreteBAdapter.class);
put(ConcreteC.class, ConcreteCAdapter.class);
}
};
@Override
public String idFromValue(Object value) {
return (classes.containsKey(value.getClass())) ? value.getClass().getName() : null;
}
@Override
public JavaType typeFromId(String id) {
try {
return classes.get(Class.forName(id)) == null ? super.typeFromId(id) : _typeFactory.constructSpecializedType(_baseType, classes.get(Class.forName(id)));
} catch (ClassNotFoundException e) {
// todo catch the e
}
return super.typeFromId(id);
}
});
mapper.setDefaultTyping(typeResolverBuilder);
return mapper;
}
そして、シリアライズ用のObjectMapper
を作成するコードを次に示します。
protected ObjectMapper getObjectMapperForSerialization() {
ObjectMapper mapper = new ObjectMapper();
StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()));
mapper.setDefaultTyping(typeResolverBuilder);
return mapper;
}
テストコード:
public static void main(String[] args) throws IOException {
JacksonUtils JacksonUtils = new JacksonUtilsImpl();
Collection<Base> data = new LinkedBlockingQueue<Base>();
data.add(new ConcreteA());
data.add(new ConcreteB());
data.add(new ConcreteC());
String json = JacksonUtils.marshallIntoString(data);
System.out.println(json);
Collection<? extends Adapter> adapters = JacksonUtils.unmarshall(json, new TypeReference<ArrayList<Adapter>>() {});
for (Adapter adapter : adapters) {
System.out.println(adapter.getClass().getName());
}
}
JacksonUtilsクラスの完全なコード:
public class JacksonUtilsImpl implements JacksonUtils {
@Override
public byte[] marshall(Collection<Base> data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream() {
@Override
public byte[] toByteArray() {
return buf;
}
};
getObjectMapperForSerialization().writerWithType(new TypeReference<Collection<Base>>() {}).writeValue(out, data);
return out.toByteArray();
}
@Override
public String marshallIntoString(Collection<Base> data) throws IOException {
return getObjectMapperForSerialization().writeValueAsString(data);
}
protected ObjectMapper getObjectMapperForSerialization() {
ObjectMapper mapper = new ObjectMapper();
StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()));
mapper.setDefaultTyping(typeResolverBuilder);
return mapper;
}
protected ObjectMapper getObjectMapperForDeserialization() {
ObjectMapper mapper = new ObjectMapper();
StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()) {
private HashMap<Class, Class> classes = new HashMap<Class, Class>() {
{
put(ConcreteA.class, ConcreteAAdapter.class);
put(ConcreteB.class, ConcreteBAdapter.class);
put(ConcreteC.class, ConcreteCAdapter.class);
}
};
@Override
public String idFromValue(Object value) {
return (classes.containsKey(value.getClass())) ? value.getClass().getName() : null;
}
@Override
public JavaType typeFromId(String id) {
try {
return classes.get(Class.forName(id)) == null ? super.typeFromId(id) : _typeFactory.constructSpecializedType(_baseType, classes.get(Class.forName(id)));
} catch (ClassNotFoundException e) {
// todo catch the e
}
return super.typeFromId(id);
}
});
mapper.setDefaultTyping(typeResolverBuilder);
return mapper;
}
@Override
public Object unmarshall(byte[] json) throws IOException {
return getObjectMapperForDeserialization().readValue(json, Object.class);
}
@Override
public <T> T unmarshall(InputStream source, TypeReference<T> typeReference) throws IOException {
return getObjectMapperForDeserialization().readValue(source, typeReference);
}
@Override
public <T> T unmarshall(byte[] json, TypeReference<T> typeReference) throws IOException {
return getObjectMapperForDeserialization().readValue(json, typeReference);
}
@Override
public <T> Collection<? extends T> unmarshall(String json, Class<? extends Collection<? extends T>> klass) throws IOException {
return getObjectMapperForDeserialization().readValue(json, klass);
}
@Override
public <T> Collection<? extends T> unmarshall(String json, TypeReference typeReference) throws IOException {
return getObjectMapperForDeserialization().readValue(json, typeReference);
}
}
このためには、JSONで追加情報を渡す必要があります。
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.PROPERTY, property="@type")
class Base {
...
}
次に、シリアル化時に@typeフィールドを追加します。
objectMapper.registerSubtypes(
new NamedType(ConcreteAAdapter.class, "ConcreteA"),
new NamedType(ConcreteBAdapter.class, "ConcreteB"),
new NamedType(ConcreteCAdapter.class, "ConcreteC")
);
// note, that for lists you need to pass TypeReference explicitly
objectMapper.writerWithType(new TypeReference<List<Base>>() {})
.writeValueAsString(someList);
{
"@type" : "ConcreteA",
...
}
逆シリアル化すると、次のようになります。
objectMapper.registerSubtypes(
new NamedType(ConcreteA.class, "ConcreteA"),
new NamedType(ConcreteB.class, "ConcreteB"),
new NamedType(ConcreteC.class, "ConcreteC")
);
objectMapper.readValue(....)
私はプログラマーブルースのアプローチが最も明確で働きやすいと思います(下の例)。私は関連する質問への彼の答えから情報を得ました: https://stackoverflow.com/a/6339600/11480 および関連するブログの投稿: http://programmerbruce.blogspot。 fi/2011/05/deserialize-json-with-jackson-into.html
また、このフレンドリーなWikiページ(Eugene Retunskyの回答でも言及されています)も確認してください。 https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
別の素晴らしいWikiページ: https://github.com/FasterXML/jackson-docs/wiki/JacksonMixInAnnotations
これはあなたにアイデアを与えるための短い例です:
ObjectMapperを次のように構成します。
mapper.getDeserializationConfig().addMixInAnnotations(Base.class, BaseMixin.class);
mapper.getSerializationConfig().addMixInAnnotations(Base.class, BaseMixin.class);
BaseMixinクラスの例(内部クラスとして定義するのは簡単です。)
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
@JsonSubTypes({
@JsonSubTypes.Type(value=ConcreteA.class, name="ConcreteA"),
@JsonSubTypes.Type(value=ConcreteB.class, name="ConcreteB")
})
private static class BaseMixin {
}
2番目のサービスでは、BaseMixinを次のように定義できます。
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
@JsonSubTypes({
@JsonSubTypes.Type(value=ConcreteAAdapter.class, name="ConcreteA"),
@JsonSubTypes.Type(value=ConcreteBAdapter.class, name="ConcreteB")
})
private static class BaseMixin {
}