У нас есть несколько JSON-POJO со сложной иерархией типов и с проблемой с аннотациями типов:
Маленький Пример:
SomeObjectA -> ObjectA -> AbstractObject
ObjectContainer
содержит AbstractObject
(ObjectA
или ObjectB
только, никогда SomeObjectA
) SomeObjectContainer
содержит SomeObjectA
только
В нашей реализации AbstractObject
будет некоторый Geo-JSON-Object.SomeObjectA
расширит Geo-JSON (с пользовательскими параметрами вместо использования properties
map), но должно иметь то же type
, что и ObjectA
.
Мы ищем правильный / лучший способаннотировать AbstractObject
для удовлетворения наших потребностей и использования с Джексоном.
Версия 1: Использование только JsonTypeInfo
:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({ //
@Type(value = ObjectA.class, name = "MY_OBJECT_A"), //
@Type(value = ObjectB.class, name = "MY_OBJECT_B") })
public abstract class AbstractObject {
public enum Type {
MY_OBJECT_A, MY_OBJECT_B
}
public Type type;
}
public class ObjectA extends AbstractObject {
public ObjectA() {
type = Type.MY_OBJECT_A;
}
public String a;
}
public class ObjectB extends AbstractObject {
public ObjectB() {
type = Type.MY_OBJECT_B;
}
public String b;
}
public class ObjectContainer {
public AbstractObject object;
}
public class SomeObjectA extends ObjectA {
public String value;
}
public class SomeObjectContainer {
public SomeObjectA object;
}
Тесты:
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import org.junit.Test;
import com.test.TestUtil;
public class ObjectATest {
@Test
public void test() throws IOException {
ObjectA object = new ObjectA();
String json = TestUtil.OBJECT_MAPPER.writeValueAsString(object);
System.out.println("json: " + json);
ObjectA actual = TestUtil.OBJECT_MAPPER.readValue(json, ObjectA.class);
System.out.println("actual: " + actual + "\n" + TestUtil.OBJECT_MAPPER.writeValueAsString(actual));
assertEquals("duplicate type", -1, json.indexOf("type", json.indexOf("type") + 4));
}
}
public class ObjectContainerTest {
@Test
public void test() throws IOException {
ObjectContainer container = new ObjectContainer();
container.object = new ObjectA();
((ObjectA) container.object).a = "test-a";
String json = TestUtil.OBJECT_MAPPER.writeValueAsString(container);
System.out.println("json: " + json);
ObjectContainer actual = TestUtil.OBJECT_MAPPER.readValue(json, ObjectContainer.class);
System.out.println("actual: " + actual + "\n" + TestUtil.OBJECT_MAPPER.writeValueAsString(actual));
assertEquals("test-a", ((ObjectA) actual.object).a);
assertEquals(ObjectA.class, actual.object.getClass());
assertEquals("duplicate type", -1, json.indexOf("type", json.indexOf("type") + 4));
}
}
public class SomeObjectContainerTest {
@Test
public void test() throws IOException {
SomeObjectContainer container = new SomeObjectContainer();
container.object = new SomeObjectA();
container.object.a = "test-a";
container.object.value = "test-value";
String json = TestUtil.OBJECT_MAPPER.writeValueAsString(container);
System.out.println("json: " + json);
SomeObjectContainer actual = TestUtil.OBJECT_MAPPER.readValue(json, SomeObjectContainer.class);
System.out.println("actual: " + actual + "\n" + TestUtil.OBJECT_MAPPER.writeValueAsString(actual));
assertEquals(container.object.value, actual.object.value);
assertEquals("duplicate type", -1, json.indexOf("type", json.indexOf("type") + 4));
}
}
Результаты теста и результаты:
ObjectATest:
json: {
"type" : "MY_OBJECT_A",
"type" : "MY_OBJECT_A"
}
FAILURE: java.lang.AssertionError: duplicate type expected:<-1> but was:<33>
ObjectContainerTest:
json: {
"object" : {
"type" : "MY_OBJECT_A",
"type" : "MY_OBJECT_A",
"a" : "test-a"
}
}
FAILURE: java.lang.AssertionError: duplicate type expected:<-1> but was:<53>
SomeObjectContainerTest:
json: {
"object" : {
"type" : "SomeObjectA",
"type" : "MY_OBJECT_A",
"a" : "test-a",
"value" : "test-value"
}
}
FAILURE: java.lang.AssertionError: duplicate type expected:<-1> but was:<53>
Версия 1 работает, но выдает дубликаты type
параметров.- Это может привести к поломке некоторых клиентов ...?
Версия 2: Использование JsonTypeInfo
и JsonTypeId
:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({ //
@Type(value = ObjectA.class, name = "MY_OBJECT_A"), //
@Type(value = ObjectB.class, name = "MY_OBJECT_B") })
public abstract class AbstractObject {
public enum Type {
MY_OBJECT_A, MY_OBJECT_B
}
@JsonTypeId
public Type type;
}
Результаты теста и результаты:
ObjectATest:
json: {
"type" : "MY_OBJECT_A"
}
SUCCESS
ObjectContainerTest:
json: {
"object" : {
"type" : "MY_OBJECT_A",
"a" : "test-a"
}
}
SUCCESS
SomeObjectContainerTest:
json: {
"object" : {
"type" : "MY_OBJECT_A",
"a" : "test-a",
"value" : "test-value"
}
}
ERROR: com.fasterxml.jackson.databind.JsonMappingException: Class com.test2.ObjectA not subtype of [simple type, class com.test2.SomeObjectA] (through reference chain: com.test2.SomeObjectContainer["object"])
Версия 2 не может прочитать SomeObjectContainer
.
Также мы пытались использовать следующие аннотации:
@JsonSubTypes({ //
@Type(value = ObjectA.class, name = "MY_OBJECT_A"), //
@Type(value = SomeObjectA.class, name = "MY_OBJECT_A"), //
@Type(value = ObjectB.class, name = "MY_OBJECT_B") })
Но это не помогло.
Почему Джексон не умеет читать SomeObjectContainer
?- object
должно быть SomeObjectA
(или некоторыми из его подклассов) и никогда не будет SomeObjectA
или ObjectA
!
Как решить эту проблему?Есть ли другая аннотация для размещения на SomeObjectContainer.object
?