Джексон - Десеріалізація за допомогою загального класу


147

У мене є рядок json, яку мені слід дезариалізувати в наступний клас

class Data <T> {
    int found;
    Class<T> hits
}

Як це зробити? Це звичайний спосіб

mapper.readValue(jsonString, Data.class);

Але як я можу зазначити, що означає Т?



Відповіді:


239

Вам потрібно створити TypeReferenceоб'єкт для кожного загального типу, який ви використовуєте, і використовувати його для десеріалізації. Наприклад -

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});

Я повинен використовувати його як TypeReference <Дані <T>> () {} ... Але я отримую таку помилку - не можу отримати доступ до приватного java.lang.class.Class () від java.lang.class. Не вдалося встановити доступ. Неможливо зробити конструктор java.lang.Class доступним
gnjago

Ні, ні Data<T>, це НЕ тип. Ви повинні вказати фактичний клас; інакше це те саме, що Data<Object>.
StaxMan

18
Що робити, якщо я не знаю, що це клас до часу виконання? Я отримаю клас як параметр під час виконання. Як ця публічна <T> void deSerialize (Клас <T> clazz {ObjectMapper mapper = новий ObjectMapper (); mapper.readValue (jsonString, новий TypeReference <Json <T>> () {});}
gnjago

1
Я задав повний питання правильно тут stackoverflow.com/questions/11659844 / ...
gnjago

яка повна назва пакета TypeReference? це com.fasterxml.jackson.core.type?
Лей Ян

88

Ви не можете цього зробити: потрібно вказати повністю розв'язаний тип, наприклад Data<MyType>. Tє просто змінною, і як це безглуздо.

Але якщо ви маєте на увазі, що Tбуде відомо, просто не статично, вам потрібно створити еквівалент TypeReferenceдинамічно. Інші питання, на які посилається, можуть вже згадувати про це, але це має виглядати приблизно так:

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}

2
Що робити, якщо я не знаю, що це клас до часу виконання? Я отримаю клас як параметр під час виконання. Як ця публічна <T> void deSerialize (Клас <T> clazz {ObjectMapper mapper = новий ObjectMapper (); mapper.readValue (jsonString, новий TypeReference <Json <T>> () {});}
gnjago

1
Я задав повний питання тут stackoverflow.com/questions/11659844 / ...
gnjago

2
Тоді просто передайте клас таким, який є, не потрібно TypeReference: У return mapper.readValue(json, clazz);чому саме тут проблема?
StaxMan

2
Проблема полягає в тому, що "Дані" - це загальний клас. Мені потрібно вказати, який тип T є під час виконання. Параметр clazz - це те, що нам під час виконання. Отже, як викликати readValue? назвавши його новим TypeReference> Json <T >> не працює повний питання тут stackoverflow.com/questions/11659844 / ...
gnjago

2
Гаразд. Тоді вам потрібно скористатися TypeFactory.. Я відредагую свою відповідь.
StaxMan

30

Перше, що ви робите - це серіалізація, потім ви можете зробити десяріалізацію.
тому, коли ви серіалізуєте, вам слід використовувати, @JsonTypeInfoщоб Джексон записував інформацію про клас у свої дані json. Що ти можеш зробити так:

Class Data <T> {
    int found;
    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    Class<T> hits
}

Тоді, коли ви дезаріалізуєте, ви виявите, що Джексон дезаріалізував ваші дані в клас, який ваші змінні звернення насправді є під час виконання.


не працює, потрапляючи нижче помилка com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Відсутній ідентифікатор типу при спробі вирішити підтип [простий тип, клас java.lang.Object]: відсутність власності типу id '@class' (для POJO власності 'data')
gaurav9620

15

Для даних класу <>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)

Зараз це застаріло.
Еван Гертіс

13

Просто напишіть статичний метод у клас Util. Я читаю Json з файлу. ви можете надати String також для readValue

public static <T> T convertJsonToPOJO(String filePath, Class<?> target) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(new File(filePath), objectMapper .getTypeFactory().constructCollectionType(List.class, Class.forName(target.getName())));
}

Використання:

List<TaskBean> list =  Util.<List<TaskBean>>convertJsonToPOJO("E:/J2eeWorkspaces/az_workspace_svn/az-client-service/dir1/dir2/filename.json", TaskBean.class);

7

Ви можете перенести його в інший клас, який знає тип вашого родового типу.

Наприклад,

class Wrapper {
 private Data<Something> data;
}
mapper.readValue(jsonString, Wrapper.class);

Тут щось конкретне. Вам потрібна обгортка для впорядкованого типу. Інакше Джексон не знає, які об’єкти створити.


6

Рядок JSON, який потрібно десеріалізувати, повинен містити інформацію про тип параметра T.
Вам потрібно буде розмістити анотації Джексона на кожному класі, який може бути переданий як параметр Tкласу, Dataщоб інформація про тип параметра Tбула прочитана / записана Джексоном у рядок JSON.

Припустимо, що це Tможе бути будь-який клас, що розширює абстрактний клас Result.

class Data <T extends Result> {
    int found;
    Class<T> hits
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ImageResult.class, name = "ImageResult"),
        @JsonSubTypes.Type(value = NewsResult.class, name = "NewsResult")})
public abstract class Result {

}

public class ImageResult extends Result {

}

public class NewsResult extends Result {

}

Після того, як кожен клас (або їх загальний супертип), який може бути переданий як параметр, T буде анотований, Джексон включить інформацію про параметр Tу JSON. Потім такий JSON можна десеріалізувати, не знаючи параметр Tпід час компіляції.
Це посилання на документацію Джексона говорить про поліморфну ​​десеріалізацію, але корисно посилатися і на це питання.


і як мені керувати цим, якщо я хочу мати Список? Скажімо, Список <ImageResult>
Лука

5

У Джексона 2.5 - це елегантний спосіб вирішити те, що використовується метод TypeFactory.constructParametricType (клас параметризований, клас ... parameterClasses), що дозволяє точно визначати Джексона JavaTypeшляхом визначення параметризованого класу та його параметризованих типів.

Припустимо, що ви хочете десаріалізувати Data<String>, ви можете зробити:

// the json variable may be a String, an InputStream and so for...
JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class);
Data<String> data = mapper.readValue(json, type);

Зауважте, що якби клас оголосив кілька параметризованих типів, це було б не важче:

class Data <T, U> {
    int found;
    Class<T> hits;
    List<U> list;
}

Ми могли б зробити:

JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);

Чудово, дякую, що це працювало на мене. За допомогою typereference я отримав виняток classcast з карти на конкретний об'єкт, але це справді робить роботу.
Tacsiazuma

1
public class Data<T> extends JsonDeserializer implements ContextualDeserializer {
    private Class<T> cls;
    public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty prop) throws JsonMappingException {
        cls = (Class<T>) ctx.getContextualType().getRawClass();
        return this;
    }
    ...
 }

0

якщо ви використовуєте scala і знаєте загальний тип під час компіляції, але не хочете вручну передавати TypeReference скрізь у всіх ваших програмах api l, ви можете використовувати наступний код (з jackson 2.9.5):

def read[T](entityStream: InputStream)(implicit typeTag: WeakTypeTag[T]): T = {

    //nathang: all of this *crazy* scala reflection allows us to handle List[Seq[Map[Int,Value]]]] without passing
    // new TypeReference[List[Seq[Map[Int,Value]]]]](){} to the function

    def recursiveFindGenericClasses(t: Type): JavaType = {
      val current = typeTag.mirror.runtimeClass(t)

      if (t.typeArgs.isEmpty) {
        val noSubtypes = Seq.empty[Class[_]]
        factory.constructParametricType(current, noSubtypes:_*)
      }

      else {
        val genericSubtypes: Seq[JavaType] = t.typeArgs.map(recursiveFindGenericClasses)
        factory.constructParametricType(current, genericSubtypes:_*)
      }

    }

    val javaType = recursiveFindGenericClasses(typeTag.tpe)

    json.readValue[T](entityStream, javaType)
  }

які можна використовувати так:

read[List[Map[Int, SomethingToSerialize]]](inputStream)

0

Щоб десаріалізувати загальний рядок JSON для Java-об’єкта з Джексоном, потрібно:

  1. Для визначення класу JSON.

  2. Виконайте відображення атрибутів.

Кінцевий код, перевірений та готовий до використання:

static class MyJSON {

    private Map<String, Object> content = new HashMap<>();

    @JsonAnySetter
    public void setContent(String key, Object value) {
        content.put(key, value);
    }
}

String json = "{\"City\":\"Prague\"}";

try {

    MyPOJO myPOJO = objectMapper.readValue(json, MyPOJO.class);

    String jsonAttVal = myPOJO.content.get("City").toString();

    System.out.println(jsonAttVal);

} catch (IOException e) {
    e.printStackTrace();
}

Важливо:
@JsonAnySetter анотація є обов'язковою, вона забезпечує загальний JSON-аналіз та сукупність.

Більш складні випадки з вкладеними масивами див. У посиланні Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object


0

Приклад не дуже хорошого, але простого рішення (не тільки для Джексона, але і для Spring RestTemplate тощо):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.