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

161

У меня есть строка json, которую я должен де-сериализовать до следующего класса

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

Как мне это сделать? Это обычный способ

mapper.readValue(jsonString, Data.class);

Но как я могу сказать, что означает T?

Gnjago
источник

Ответы:

257

Вам необходимо создать TypeReferenceобъект для каждого используемого универсального типа и использовать его для десериализации. Например -

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});
Эсер Айгюн
источник
Я должен использовать его как TypeReference <Data <T>> () {} ... Но я получаю следующую ошибку - не могу получить доступ к частному java.lang.class.Class () из java.lang.class. Не удалось установить доступ. Невозможно сделать конструктор java.lang.Class доступным
gnjago
Нет, Data<T>это НЕ тип. Вы должны указать фактический класс; в остальном то же, что и Data<Object>.
StaxMan
21
Что, если я не знаю, что это за класс до времени выполнения? Я получу класс в качестве параметра во время выполнения. Как этот public <T> void deSerialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago
2
Я правильно задал полный вопрос здесь stackoverflow.com/questions/11659844/…
gnjago
какое полное имя пакета TypeReference? это так com.fasterxml.jackson.core.type?
Лэй Ян
89

Вы не можете этого сделать: вы должны указать полностью разрешенный тип, например 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);
}
StaxMan
источник
2
Что, если я не знаю, что это за класс до времени выполнения? Я получу класс в качестве параметра во время выполнения. Как этот public <T> void deSerialize (Class <T> clazz {ObjectMapper mapper = new ObjectMapper (); mapper.readValue (jsonString, new TypeReference <Json <T>> () {});}
gnjago
1
Я задал полный вопрос здесь stackoverflow.com/questions/11659844/…
gnjago
2
Затем просто передайте класс как есть, не нужно TypeReference: в return mapper.readValue(json, clazz);чем именно проблема?
StaxMan
2
Проблема в том, что «Data» - это общий класс. Мне нужно указать, какой тип T находится во время выполнения. Параметр clazz - это то, что используется во время выполнения. Итак, как вызвать readValue? вызов его с помощью new 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
}

Затем, когда вы десериализуете, вы обнаружите, что Джексон десериализовал ваши данные в класс, который ваша переменная фактически выполняет во время выполнения.

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

Для класса Data <>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)
Девеш
источник
1
Теперь это устарело.
Эван Гертис,
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);
AZ_
источник
7

Вы можете обернуть его в другой класс, который знает тип вашего универсального типа.

Например,

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

Вот что-то конкретное. Вам нужна обертка для каждого овеществленного типа. В противном случае Джексон не знает, какие объекты создавать.

Sksamuel
источник
7

В Jackson 2.5 появился элегантный способ решения, использующий метод TypeFactory.constructParametricType (Class параметризованный, Class ... parameterClasses), который позволяет напрямую определить Jackson 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);
davidxxx
источник
Отлично, спасибо, у меня сработало. С помощью typereference я получил исключение classcast от карты к конкретному объекту, но это действительно работает.
Tacsiazuma
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во время компиляции.
Эта ссылка на документацию Джексона говорит о полиморфной десериализации, но также полезна для ответа на этот вопрос.

Санджив Сачдев
источник
и как мне управлять этим, если я хочу иметь список? Скажем, List <ImageResult>
Лука Арчидиаконо
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;
    }
    ...
 }
mikka22
источник
0

если вы используете scala и знаете общий тип во время компиляции, но не хотите вручную передавать TypeReference везде во всех ваших API, вы можете использовать следующий код (с 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-объект с помощью Jackson, вам необходимо:

  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

Майк Б.
источник
-2

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

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Демель
источник