Джексон десериализует вложенный полиморфный тип

Я пытаюсь использовать Jakson для десериализации вложенного полиморфного типа. То есть мой тип верхнего уровня относится к другому полиморфному типу, который, наконец, расширяется классом, который не является абстрактным. Это не работает и создает исключение.

Вот сокращенный пример того, что я пытаюсь сделать.

package com.adfin;

import junit.framework.TestCase;
import org.codehaus.jackson.annotate.JsonSubTypes;
import org.codehaus.jackson.annotate.JsonTypeInfo;
import org.codehaus.jackson.map.ObjectMapper;

import java.io.IOException;

public class JaksonDouble extends TestCase {

  @JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "name"
  )
  @JsonSubTypes({
    @JsonSubTypes.Type(value = SecondLevel.class, name = "SECOND")
  })
  public static abstract class FirstLevel {
    public abstract String getTestValue();
  }

  @JsonTypeInfo(
    use = JsonTypeInfo.Id.CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class"
  )
  public static abstract class SecondLevel extends FirstLevel {

  }

  public static class FinalLevel extends SecondLevel {
    String test;
    @Override public String getTestValue() { return test; }
  }

  public void testDoubleAbstract() throws IOException {
    String testStr = "{ \"name\": \"SECOND\", \"@class\": \"com.adfin.JasksonDouble.FinalLevel\", \"test\": \"foo\"}";

    ObjectMapper mapper = new ObjectMapper();
    FirstLevel result = mapper.readValue(testStr, FirstLevel.class);
  }
}

Я получаю стандартное исключение об абстрактных типах.

org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.adfin.JaksonDouble$SecondLevel, problem: abstract types can only be instantiated with additional type information at [Source: java.io.StringReader@f2a55aa; line: 1, column: 19]

Позвольте мне объяснить мой вариант использования. У меня есть документ Json, описывающий рабочий процесс с данными. У меня есть абстрактный тип на "первом уровне", описывающий операцию с одним значением. Я получил несколько классов, которые не являются абстрактными и реализуют общие операции (я аннотирую их все с помощью @JsonSubTypes).

У меня есть один специальный @JsonSubTypes, который называется "CUSTOM". Это еще один абстрактный класс, представляющий пользовательские операции, которые кто-то написал (за пределами обычного jar-файла), и они могут указать полное имя класса, используя свойство "@class". Похоже, что синтаксический анализатор Jakson никогда не читает аннотацию @JsonTypeInfo во втором классе класса.

Как я могу сделать эту работу. Или, по крайней мере, как я могу заставить этот вариант использования работать.

2 ответа

Решение

Ваши определения испорчены - вы пытаетесь использовать два идентификатора типа: имя типа И класс. Это не имеет никакого смысла. Вы должны выбрать один или другой метод, а не оба.

Если вы выбираете имя класса Java в качестве информации о типе, просто не указывайте имя. Кроме того, вам нужно только включить @JsonTypeInfo за FirstLevel; подклассы наследуют это определение.

Если вы предпочитаете использовать имя логического типа, удалите свойство класса. Вам также нужно будет указать список подтипов, либо с аннотацией, либо путем регистрации их через ObjectMapper,

Прежде всего, имя класса в вашем json неверно, оно должно быть com.adfin.JasksonDouble$FinalLevel, доллар вместо точки.

Вот код, который работает, но я не уверен, правильно ли он отвечает на все подтипы.

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public static abstract class FirstLevel {
    public abstract String getTestValue();
}

Удалите аннотации из других классов, и это должно работать (только что проверил).

Однако все это выглядит довольно сложно, если вы можете попробовать другую библиотеку, возможно, вы захотите взглянуть на Genson. Чтобы включить поддержку полиморфных типов, вы должны настроить свой экземпляр Genson. Если вы также производите json, вам больше ничего не нужно (поскольку вы можете использовать Genson для создания потока json, необходимого для обработки полиморфных типов).

Вот пример:

// enable polymorphic types support
Genson genson = new Genson.Builder().setWithClassMetadata(true).create();
// the @class must be first property in the json object
String testStr = "{ \"@class\": \"com.adfin.JasksonDouble$FinalLevel\", \"test\": \"foo\"}";
FirstLevel result = genson.deserialize(testStr, FirstLevel.class);
System.out.println(result.getTestValue());

Еще одна приятная вещь с Genson - это то, что он дает вам возможность регистрировать псевдонимы для ваших классов, так что вам не нужно делать всю информацию о пакете доступной в вашем потоке. Другое преимущество заключается в том, что если вы храните поток json в базе данных и перемещаете свой класс в другой пакет, вам нужно только изменить псевдоним в своем приложении, а не переносить все потоки json из БД.

Genson genson = new Genson.Builder().setWithClassMetadata(true)
                                    .addAlias("FinalLevel", FinalLevel.class)
                                    .create();
// the @class must be first property in the json object
String testStr = "{ \"@class\": \"FinalLevel\", \"test\": \"foo\"}";
FirstLevel result = genson.deserialize(testStr, FirstLevel.class);
System.out.println(result.getTestValue());
Другие вопросы по тегам