Jackson - desserializar usando classe genérica

Respostas:

238

Você precisa criar um TypeReferenceobjeto para cada tipo genérico que você usar e usar isso para desserialização. Por exemplo -

mapper.readValue(jsonString, new TypeReference<Data<String>>() {});
Eser Aygün
fonte
Eu tenho que usá-lo como TypeReference <Data <T>> () {} ... Mas estou recebendo o seguinte erro - não é possível acessar java.lang.class.Class () privado de java.lang.class. Falha ao definir o acesso. Não pode fazer um construtor java.lang.Class acessível
gnjago
Não, não Data<T>, esse NÃO é um tipo. Você deve especificar a classe real; caso contrário, é o mesmo que Data<Object>.
StaxMan
18
E se eu não souber que classe é essa até o tempo de execução? Receberei a classe como parâmetro durante o tempo de execução. Como esse público <T> vazio Deserialize (Class <T> clazz {ObjectMapper mapeador = new ObjectMapper (); mapper.readValue (jsonString, novo TypeReference <Json <T >> () {});}
gnjago
1
Eu fiz a pergunta completa corretamente aqui stackoverflow.com/questions/11659844/…
gnjago 27/07/2012
qual é o nome completo do pacote TypeReference? é isso com.fasterxml.jackson.core.type?
Lei Yang
88

Você não pode fazer isso: você deve especificar o tipo totalmente resolvido, como Data<MyType>. Té apenas uma variável e, como não tem sentido.

Mas se você quer dizer que Tisso será conhecido, apenas não estaticamente, você precisará criar o equivalente TypeReferencedinamicamente. Outras perguntas mencionadas já podem mencionar isso, mas deve ser algo como:

public Data<T> read(InputStream json, Class<T> contentClass) {
   JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, contentClass);
   return mapper.readValue(json, type);
}
StaxMan
fonte
2
E se eu não souber que classe é essa até o tempo de execução? Receberei a classe como parâmetro durante o tempo de execução. Como esse público <T> vazio Deserialize (Class <T> clazz {ObjectMapper mapeador = new ObjectMapper (); mapper.readValue (jsonString, novo TypeReference <Json <T >> () {});}
gnjago
1
Eu fiz a pergunta completa aqui stackoverflow.com/questions/11659844/…
gnjago
2
Depois é só passar na aula como está, não há necessidade TypeReference: return mapper.readValue(json, clazz);Qual é exatamente o problema aqui?
27412 StaxMan
2
O problema é que "Dados" é uma classe genérica. Preciso especificar qual tipo T é em tempo de execução. O parâmetro clazz é o que nos usamos em tempo de execução. Então, como chamar readValue? chamá-lo com nova TypeReference> Json <T >> não funciona A questão completo é aqui stackoverflow.com/questions/11659844/...
gnjago
2
Está bem. Então você precisa usar TypeFactory.. Vou editar minha resposta.
27412 StaxMan
30

A primeira coisa que você faz é serializar, depois pode desserializar.
portanto, ao serializar, você deve @JsonTypeInfopermitir que jackson escreva informações de classe em seus dados json. O que você pode fazer é assim:

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

Então, quando você desserializar, você descobrirá que jackson desserializou seus dados em uma classe na qual suas ocorrências variáveis ​​realmente estão no tempo de execução.

CharlieQ
fonte
não está funcionando, ficando abaixo do erro com.fasterxml.jackson.databind.exc.InvalidTypeIdException: ID de tipo ausente ao tentar resolver o subtipo de [tipo simples, classe java.lang.Object]: propriedade de identificação de tipo ausente '@class' (para POJO propriedade 'data')
gaurav9620 08/04
15

Para a classe Data <>

ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().constructParametrizedType(Data.class, Data.class, Parameter.class);
Data<Parameter> dataParam = mapper.readValue(jsonString,type)
Devesh
fonte
Agora está obsoleto.
Evan Gertis 30/06
13

Basta escrever um método estático na classe Util. Estou lendo um Json de um arquivo. você pode dar String também para 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())));
}

Uso:

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

Você pode agrupá-lo em outra classe que sabe o tipo do seu tipo genérico.

Por exemplo,

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

Aqui, algo é do tipo concreto. Você precisa de um invólucro por tipo reificado. Caso contrário, Jackson não sabe quais objetos criar.

sksamuel
fonte
6

A sequência JSON que precisa ser desserializada precisará conter as informações de tipo sobre o parâmetro T.
Você precisará colocar anotações de Jackson em todas as classes que podem ser passadas como parâmetro Tpara classe, Datapara que as informações sobre o tipo de parâmetro Tpossam ser lidas / gravadas na string JSON por Jackson.

Vamos supor que Tpossa ser qualquer classe que estenda a classe abstrata 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 {

}

Depois que cada classe (ou seu supertipo comum) que pode ser passado como parâmetro T for anotado, Jackson incluirá informações sobre o parâmetro Tno JSON. Esse JSON pode então ser desserializado sem conhecer o parâmetro Tno tempo de compilação.
Este link da documentação de Jackson fala sobre desserialização polimórfica, mas também é útil para se referir a essa pergunta.

Sanjeev Sachdev
fonte
e como eu gerencio isso se eu quero ter uma lista? Como se diz Lista <ImageResult>
Luca Archidiacono
5

No Jackson 2.5, uma maneira elegante de resolver isso é usar o método TypeFactory.constructParametricType (Classe parametrizada, Classe ... parameterClasses) que permite definir diretamente um Jackson JavaTypeespecificando a classe parametrizada e seus tipos parametrizados.

Supondo que você deseja desserializar Data<String>, você pode:

// 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);

Observe que se a classe declarasse vários tipos com parâmetros, não seria muito mais difícil:

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

Nós poderíamos fazer :

JavaType type = mapper.getTypeFactory().constructParametricType(Data.class, String.class, Integer);
Data<String, Integer> data = mapper.readValue(json, type);
davidxxx
fonte
Incrível, obrigado, funcionou para mim. Com a typereference, obtive a exceção do classcast do mapa para o objeto específico, mas isso realmente funciona.
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;
    }
    ...
 }
mikka22
fonte
0

se você estiver usando scala e souber o tipo genérico em tempo de compilação, mas não quiser passar manualmente TypeReference em todos os lugares em todos os seus aplicativos API, poderá usar o seguinte código (com 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)
  }

que pode ser usado assim:

read[List[Map[Int, SomethingToSerialize]]](inputStream)
nathan g
fonte
0

Para desserializar uma string JSON genérica para um objeto Java com Jackson, você precisa:

  1. Para definir uma classe JSON.

  2. Execute um mapeamento de atributos.

Código final, testado e pronto para ser usado:

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();
}

Importante: a
@JsonAnySetter anotação é obrigatória, assegura uma análise e uma população JSON genéricas.

Para casos mais complicados com matrizes aninhadas, consulte a referência da Baeldung: https://www.baeldung.com/jackson-mapping-dynamic-object

Mike B.
fonte
0

Exemplo de decisão não muito boa, mas simples (não apenas para Jackson, também para Spring RestTemplate, etc.):

Set<MyClass> res = new HashSet<>();
objectMapper.readValue(json, res.getClass());
Demel
fonte