2015-07-28 2 views
26

Я могу сериализацию и десериализации иерархии классов, где абстрактный базовый класс с аннотациейЯвляется ли Jackson's @JsonSubTypes еще необходимым для полиморфной десериализации?

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS, 
    include = JsonTypeInfo.As.PROPERTY, 
    property = "@class") 

, но не @JsonSubTypes листингом подклассов, а сами подклассы являются относительно Неаннотированными, имея только @JsonCreator на конструкторе. ObjectMapper - ваниль, и я не использую mixin.

Джексон документация на PolymorphicDeserialization and "type ids" предлагает (сильно) мне нужно @JsonSubTypes аннотацию на абстрактный базовый класс, или использовать его на Mixin, или что мне нужно register the subtypes with the ObjectMapper. И есть много вопросов SO и/или сообщений в блоге, которые согласны. Но это работает. (Это Джексон 2.6.0.)

Итак ... я бенефициар пока еще не документировано, или я полагаться на незарегистрированном поведении (что может измениться) или что-то еще? (Я спрашиваю, потому что я действительно не хочу, чтобы он был одним из двух последних. Но I gots to know.)

EDIT: Добавление кода - и один комментарий. Комментарий: я должен был упомянуть, что все подклассы, которые я десериализую, находятся в одном пакете и в той же самой бане, что и базовый абстрактный класс.

Абстрактный базовый класс:

package so; 
import com.fasterxml.jackson.annotation.JsonTypeInfo; 

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS, 
    include = JsonTypeInfo.As.PROPERTY, 
    property = "@class") 
public abstract class PolyBase 
{ 
    public PolyBase() { } 

    @Override 
    public abstract boolean equals(Object obj); 
} 

Подкласс него:

package so; 
import org.apache.commons.lang3.builder.EqualsBuilder; 
import com.fasterxml.jackson.annotation.JsonCreator; 
import com.fasterxml.jackson.annotation.JsonProperty; 

public final class SubA extends PolyBase 
{ 
    private final int a; 

    @JsonCreator 
    public SubA(@JsonProperty("a") int a) { this.a = a; } 

    public int getA() { return a; } 

    @Override 
    public boolean equals(Object obj) { 
     if (null == obj) return false; 
     if (this == obj) return true; 
     if (this.getClass() != obj.getClass()) return false; 

     SubA rhs = (SubA) obj; 
     return new EqualsBuilder().append(this.a, rhs.a).isEquals(); 
    } 
} 

Подклассы SubB и SubC являются одинаковыми, за исключением того поля a объявлен String (не int) в SubB и boolean (не int) в SubC (и метод getA является modifi соответственно).

класс

Тест:

package so;  
import java.io.IOException; 
import org.apache.commons.lang3.builder.EqualsBuilder; 
import org.testng.annotations.Test; 
import static org.assertj.core.api.Assertions.*; 
import com.fasterxml.jackson.annotation.JsonCreator; 
import com.fasterxml.jackson.annotation.JsonProperty; 
import com.fasterxml.jackson.databind.ObjectMapper; 

public class TestPoly 
{ 
    public static class TestClass 
    { 
     public PolyBase pb1, pb2, pb3; 

     @JsonCreator 
     public TestClass(@JsonProperty("pb1") PolyBase pb1, 
         @JsonProperty("pb2") PolyBase pb2, 
         @JsonProperty("pb3") PolyBase pb3) 
     { 
      this.pb1 = pb1; 
      this.pb2 = pb2; 
      this.pb3 = pb3; 
     } 

     @Override 
     public boolean equals(Object obj) { 
      if (null == obj) return false; 
      if (this == obj) return true; 
      if (this.getClass() != obj.getClass()) return false; 

      TestClass rhs = (TestClass) obj; 
      return new EqualsBuilder().append(pb1, rhs.pb1) 
             .append(pb2, rhs.pb2) 
             .append(pb3, rhs.pb3) 
             .isEquals(); 
     } 
    } 

    @Test 
    public void jackson_should_or_should_not_deserialize_without_JsonSubTypes() { 

     // Arrange 
     PolyBase pb1 = new SubA(5), pb2 = new SubB("foobar"), pb3 = new SubC(true); 
     TestClass sut = new TestClass(pb1, pb2, pb3); 

     ObjectMapper mapper = new ObjectMapper(); 

     // Act 
     String actual1 = null; 
     TestClass actual2 = null; 

     try { 
      actual1 = mapper.writeValueAsString(sut); 
     } catch (IOException e) { 
      fail("didn't serialize", e); 
     } 

     try { 
      actual2 = mapper.readValue(actual1, TestClass.class); 
     } catch (IOException e) { 
      fail("didn't deserialize", e); 
     } 

     // Assert 
     assertThat(actual2).isEqualTo(sut); 
    } 
} 

Этот тест проходит, и если вы нарушите на второй try { линии вы можете проверить actual1 и посмотреть:

{"pb1":{"@class":".SubA","a":5}, 
"pb2":{"@class":".SubB","a":"foobar"}, 
"pb3":{"@class":".SubC","a":true}} 

Таким образом, три подклассы получили правильно сериализовать (каждый с их имя класса как id), а затем десериализован, а результат сравним с равным (каждый подкласс имеет «тип значения» equals()).

+5

Хороший вопрос, хороший пример. Показывает хорошие исследования. Нужно больше. –

ответ

26

Существует два способа достижения полиморфизма при сериализации и десериализации с помощью Джексона. Они определены в Раздел 1. Использование в link вы опубликовали.

Ваш код

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS, 
    include = JsonTypeInfo.As.PROPERTY, 
    property = "@class") 

пример второго подхода.Первое, что нужно отметить, что

Все экземпляры аннотированный типа и его подтипы использовать эти параметры (если не заменится другим аннотацию)

Так это значение конфигурации распространяется на все подтипы. Затем нам нужен идентификатор типа, который отобразит тип Java в текстовое значение в строке JSON и наоборот. В вашем примере это дается JsonTypeInfo.Id#MINIMAL_CLASS

Означает, что имя класса Java с минимальным путем используется в качестве идентификатора типа.

Таким образом, минимальное имя класса генерируется из целевого экземпляра и записывается в содержимое JSON при сериализации. Или минимальное имя класса используется для определения целевого типа для десериализации.

Вы могли бы также использоваться JsonTypeInfo.Id#NAME которые

Означает, что логического имени типа используется в качестве информации о типе; имя будет , тогда необходимо отдельно разделить конкретный конкретный тип (Class).

Чтобы обеспечить такой логическое имя типа, вы используете @JsonSubTypes

аннотацию, используемую с JsonTypeInfo для обозначения вспомогательных типов сериализуемых полиморфных типов, а также к ассоциированным логическим именам используемых в содержании JSON (который более переносим, ​​чем использование физических имен Java ).

Это еще один способ добиться такого же результата. Документация вы спрашиваете о состояниях

Тип идентификаторы, которые основаны на имени класса Java довольно прямо вперед: это просто имя класса, возможно, некоторые просто префикс удаления (для «минимальной» варианты). Но имя типа отличается: у одного есть , чтобы иметь сопоставление между логическим именем и фактическим классом.

Так что различные значения JsonTypeInfo.Id, имеющие отношение к именам классов, являются прямыми, поскольку они могут быть сгенерированы автоматически. Однако для имен типов вам нужно явно указать значение отображения.

+1

Спасибо, что объяснили, что @JsonSubTypes необходимо, только если вы используете форму NAME. Вся документация, которую я читал, и я не понял этого ... – davidbak

+6

Обман заключается в том, что если вы используете форму имени (что очень желательно), вы не сможете динамически добавлять класс или добавлять классы позже. – HDave

+3

Не использует JsonTypeInfo для отдельных конкретных классов? Использование этого интерфейса по интерфейсу, похоже, нарушает принцип open-close, я полагаю (как нам нужно изменить наш интерфейс при каждом новом добавлении конкретной реализации). Пожалуйста, поправьте меня, если я что-то упустил – Harshit

Смежные вопросы