Полиморфизм с Джексоном: как использовать аннотации JsonTypeInfo, JsonSubTypes и JsonTypeId, если подтип расширен и используется явно? - PullRequest
0 голосов
/ 30 мая 2018

У нас есть несколько 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?

...