Jackson e referência de tipo genérico

107

Quero usar a biblioteca json jackson para um método genérico da seguinte maneira:

public MyRequest<T> tester() {
    TypeReference<MyWrapper<T>> typeRef = new TypeReference<MyWrapper<T>>();  
    MyWrapper<T> requestWrapper = (MyWrapper<T>) JsonConverter.fromJson(jsonRequest, typeRef);
    return requestWrapper.getRequest();
}

...

public class MyWrapper<T> {

    private MyRequest<T> request;

    public MyRequest<T> getRequest() {
        return request;
    }

    public void setRequest(MyRequest<T> request) {
        this.request = request;
    }
}


 public class MyRequest{
     private List<T> myobjects;

     public void setMyObjects(List<T> ets) {
         this.myobjects = ets;
     }

     @NotNull
     @JsonIgnore
     public T getMyObject() {
         return myobjects.get(0);
     }
}

Agora, o problema é que, quando chamo getMyObject (), que está dentro do objeto de solicitação, jackson retorna o objeto personalizado aninhado como um LinkedHashMap. Existe alguma maneira de especificar que o objeto T precisa ser retornado ?. Por exemplo: se eu enviei um objeto do tipo Cliente, então o Cliente deve ser retornado dessa Lista ?.

Obrigado.

Techzen
fonte
Adicione a implementação de getT ()
Jim Garrison
Esta pergunta é semelhante a stackoverflow.com/questions/6062011/… mas eles sugeriram especificar o tipo usando TypeFactory. No entanto, não sei o tipo em tempo de compilação ...
techzen
TypeFactory possui métodos que não precisam de classe estática; createCollectionType e assim por diante.
StaxMan
Por favor, compartilhe o código completo. Eu também estou enfrentando o mesmo problema.
AZ_
Não é TypeReferenceabstrato?
Kyle Delaney

Respostas:

194

Este é um problema bem conhecido com o apagamento de tipo Java: T é apenas uma variável de tipo e você deve indicar a classe real, geralmente como argumento de classe. Sem essas informações, o melhor que pode ser feito é usar limites; e o T simples é quase o mesmo que 'T estende o objeto'. E Jackson irá vincular objetos JSON como mapas.

Nesse caso, o método do testador precisa ter acesso à classe, e você pode construir

JavaType type = mapper.getTypeFactory().
  constructCollectionType(List.class, Foo.class)

e depois

List<Foo> list = mapper.readValue(new File("input.json"), type);
StaxMan
fonte
16
Funciona: eu fiz o seguinte: JavaType topMost = mapper.getTypeFactory (). ConstructParametricType (MyWrapper.class, ActualClassRuntime.class); e então fiz o readValue e finalmente funcionou :)
techzen
Sim, isso funciona - obrigado por apontar o método para a criação de um tipo genérico diferente do tipo Mapa / Coleção!
StaxMan
1
@StaxMan seria melhor usar ClassMate para esse tipo de coisa a partir de agora?
Husayt
2
@husayt sim, tecnicamente a biblioteca java-classmate é superior. Mas integrá-lo com Jackson é um pouco complicado apenas porque a abstração de tipo do próprio Jackson é parte integrante da API. A longo prazo, seria ótimo descobrir uma maneira adequada de fazer Jackson usar o código do colega de classe, incorporado ou via dep.
StaxMan
1
Eu sinto que Jackson não deveria ter que cobrir o que parecem ser lacunas nos genéricos, mas de qualquer forma, ele o faz muito bem.
Adrian Baker
6

'JavaType' funciona !! Eu estava tentando desempacotar (desserializar) uma lista em string json para objetos java ArrayList e estava lutando para encontrar uma solução desde dias.
Abaixo está o código que finalmente me deu a solução. Código:

JsonMarshallerUnmarshaller<T> {
    T targetClass;

    public ArrayList<T> unmarshal(String jsonString) {
        ObjectMapper mapper = new ObjectMapper();

        AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();
        mapper.getDeserializationConfig()
            .withAnnotationIntrospector(introspector);

        mapper.getSerializationConfig()
            .withAnnotationIntrospector(introspector);
        JavaType type = mapper.getTypeFactory().
            constructCollectionType(
                ArrayList.class, 
                targetclass.getClass());

        try {
            Class c1 = this.targetclass.getClass();
            Class c2 = this.targetclass1.getClass();
            ArrayList<T> temp = (ArrayList<T>) 
                mapper.readValue(jsonString,  type);
            return temp ;
        } catch (JsonParseException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null ;
    }  
}
rushidesai1
fonte
Como inicializar o TargetClass?
AZ_
Por favor, mostre-me um pequeno exemplo. Estou passando Class <?> Target e recebendo target.getClassName ().
AZ_
1
Adicione um construtor da seguinte maneira: JsonMarshallerUnmarshaller <T> {private Class <T> targetClass; JsonMarshallerUnmarshaller (Classe <T> c) {targetClass = c; }} Faça as alterações apropriadas agora na função 'unmarshal' para usar essa classe em vez de fazer getClass em todos os lugares.
rushidesai1
Algumas notas: o código pode ser bastante simplificado observando que todas as exceções são subtipos de IOException(precisa apenas de uma captura), e que o introspector de anotação padrão já é JacksonAnnotationIntrospector- portanto, não há necessidade de fazer nada ObjectMapper, apenas construa e funciona.
StaxMan
Então esse código eu nem consigo compilar. Tem algum exemplo ao vivo para colar no lugar?
Chave
0

Eu modifiquei a resposta de rushidesai1 para incluir um exemplo de trabalho.

JsonMarshaller.java

import java.io.*;
import java.util.*;

public class JsonMarshaller<T> {
    private static ClassLoader loader = JsonMarshaller.class.getClassLoader();

    public static void main(String[] args) {
        try {
            JsonMarshallerUnmarshaller<Station> marshaller = new JsonMarshallerUnmarshaller<>(Station.class);
            String jsonString = read(loader.getResourceAsStream("data.json"));
            List<Station> stations = marshaller.unmarshal(jsonString);
            stations.forEach(System.out::println);
            System.out.println(marshaller.marshal(stations));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("resource")
    public static String read(InputStream ios) {
        return new Scanner(ios).useDelimiter("\\A").next(); // Read the entire file
    }
}

Resultado

Station [id=123, title=my title, name=my name]
Station [id=456, title=my title 2, name=my name 2]
[{"id":123,"title":"my title","name":"my name"},{"id":456,"title":"my title 2","name":"my name 2"}]

JsonMarshallerUnmarshaller.java

import java.io.*;
import java.util.List;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;

public class JsonMarshallerUnmarshaller<T> {
    private ObjectMapper mapper;
    private Class<T> targetClass;

    public JsonMarshallerUnmarshaller(Class<T> targetClass) {
        AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();

        mapper = new ObjectMapper();
        mapper.getDeserializationConfig().with(introspector);
        mapper.getSerializationConfig().with(introspector);

        this.targetClass = targetClass;
    }

    public List<T> unmarshal(String jsonString) throws JsonParseException, JsonMappingException, IOException {
        return parseList(jsonString, mapper, targetClass);
    }

    public String marshal(List<T> list) throws JsonProcessingException {
        return mapper.writeValueAsString(list);
    }

    public static <E> List<E> parseList(String str, ObjectMapper mapper, Class<E> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return mapper.readValue(str, listType(mapper, clazz));
    }

    public static <E> List<E> parseList(InputStream is, ObjectMapper mapper, Class<E> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return mapper.readValue(is, listType(mapper, clazz));
    }

    public static <E> JavaType listType(ObjectMapper mapper, Class<E> clazz) {
        return mapper.getTypeFactory().constructCollectionType(List.class, clazz);
    }
}

Station.java

public class Station {
    private long id;
    private String title;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return String.format("Station [id=%s, title=%s, name=%s]", id, title, name);
    }
}

data.json

[{
  "id": 123,
  "title": "my title",
  "name": "my name"
}, {
  "id": 456,
  "title": "my title 2",
  "name": "my name 2"
}]
Sr. Polywhirl
fonte