Desserialize JSON para ArrayList <POJO> usando Jackson

97

Tenho uma classe Java MyPojoque estou interessado em desserializar do JSON. Eu configurei uma classe MixIn especial,, MyPojoDeMixInpara me ajudar com a desserialização. MyPojotem apenas inte Stringvariáveis ​​de instância combinadas com getters e setters adequados. MyPojoDeMixInparece algo assim:

public abstract class MyPojoDeMixIn {
  MyPojoDeMixIn(
      @JsonProperty("JsonName1") int prop1,
      @JsonProperty("JsonName2") int prop2,
      @JsonProperty("JsonName3") String prop3) {}
}

No meu cliente de teste eu faço o seguinte, mas é claro que não funciona em tempo de compilação porque há JsonMappingExceptionuma incompatibilidade de tipo relacionada.

ObjectMapper m = new ObjectMapper();
m.getDeserializationConfig().addMixInAnnotations(MyPojo.class,MyPojoDeMixIn.class);
try { ArrayList<MyPojo> arrayOfPojo = m.readValue(response, MyPojo.class); }
catch (Exception e) { System.out.println(e) }

Estou ciente de que poderia aliviar esse problema criando um objeto "Resposta" que contenha apenas um ArrayList<MyPojo>, mas, então, teria de criar esses objetos um tanto inúteis para cada tipo que desejo retornar.

Eu também olhei online em JacksonInFiveMinutes, mas tive um péssimo momento para entender as coisas Map<A,B>e como elas se relacionam com o meu problema. Se você não sabe, sou totalmente novo em Java e venho de uma formação Obj-C. Eles mencionam especificamente:

Além da vinculação a POJOs e tipos "simples", há uma variante adicional: a vinculação a contêineres genéricos (tipificados). Este caso requer tratamento especial devido ao chamado Type Erasure (usado por Java para implementar genéricos de uma maneira compatível com versões anteriores), que impede você de usar algo como Collection.class (que não compila).

Portanto, se você quiser vincular dados a um mapa, precisará usar:

Map<String,User> result = mapper.readValue(src, new TypeReference<Map<String,User>>() { });

Como posso desserializar diretamente para ArrayList?

tacos_tacos_tacos
fonte

Respostas:

149

Você pode desserializar diretamente para uma lista usando o TypeReferencewrapper. Um exemplo de método:

public static <T> T fromJSON(final TypeReference<T> type,
      final String jsonPacket) {
   T data = null;

   try {
      data = new ObjectMapper().readValue(jsonPacket, type);
   } catch (Exception e) {
      // Handle the problem
   }
   return data;
}

E é usado assim:

final String json = "";
Set<POJO> properties = fromJSON(new TypeReference<Set<POJO>>() {}, json);

TypeReference Javadoc

Percepção
fonte
Sua resposta parece estar relacionada às informações deles sobre como usar o suporte integrado para TypeReference- eu simplesmente não entendo como fazer isso ... Consulte minha edição acima para obter as instruções sobre como usar os genéricos.
tacos_tacos_tacos
Bem, está relacionado. Mas este é um trecho do código de trabalho em produção. Esqueça o seu mixin, apenas use o código que mostrei (mas substitua POJO, é claro, pelo nome da sua classe de bean real).
Percepção de
Seu código compilou, mas recebo uma exceção de tempo de execução ao tentar imprimir o arrayList.toString()about a NullPointerException. Suponho que isso possa ser porque meu POJOnão está de acordo com as convenções de nomenclatura corretas para suas propriedades, ou seja, todo o problema é que o serviço da web retorna Prop1Membere meu objeto sim Prop1. Este é o único motivo real pelo qual estou usando mixins para começar, então não tenho que colocar as declarações for @JsonPropertyem meus objetos puros.
tacos_tacos_tacos
1
Inspecione visualmente seu array para ter certeza de ter pelo menos uma lista. E, se necessário, adicione o mixin de volta, que deve funcionar junto com o TypeReference para deserializar tudo perfeitamente.
Percepção de
2
@JsonProperty não é tão maligno quanto as pessoas fazem parecer. É difícil fugir das anotações específicas do fornecedor com o estado atual de padronização (mínimo) no campo.
Percepção de
104

Outra maneira é usar uma matriz como um tipo, por exemplo:

ObjectMapper objectMapper = new ObjectMapper();
MyPojo[] pojos = objectMapper.readValue(json, MyPojo[].class);

Dessa forma, você evita todo o incômodo com o objeto Type e, se realmente precisar de uma lista, pode sempre converter a matriz em uma lista:

List<MyPojo> pojoList = Arrays.asList(pojos);

IMHO isso é muito mais legível.

E para torná-la uma lista real (que pode ser modificada, veja as limitações de Arrays.asList()), faça o seguinte:

List<MyPojo> mcList = new ArrayList<>(Arrays.asList(pojos));
DevNG
fonte
1
Elegante, com certeza, mas não consigo generificá-lo por causa da classe MyPojo []., Que não quero passar como parâmetro.
Adrian Redgers
Acho que usar TypeFactoryconforme descrito na próxima resposta: stackoverflow.com/a/42458104/91497 é a maneira Jackson de especificar o tipo.
Jmini
32

Esta variante parece mais simples e elegante.

CollectionType typeReference =
    TypeFactory.defaultInstance().constructCollectionType(List.class, Dto.class);
List<Dto> resultDto = objectMapper.readValue(content, typeReference);
Дмитрий Кулешов
fonte
3

Eu também estou tendo o mesmo problema. Eu tenho um json que deve ser convertido em ArrayList.

A conta tem esta aparência.

Account{
  Person p ;
  Related r ;

}

Person{
    String Name ;
    Address a ;
}

Todas as classes acima foram anotadas corretamente. Tentei TypeReference> () {} mas não está funcionando.

Ele me dá Arraylist, mas ArrayList tem um linkedHashMap que contém mais alguns hashmaps vinculados contendo valores finais.

Meu código é o seguinte:

public T unmarshal(String responseXML,String c)
{
    ObjectMapper mapper = new ObjectMapper();

    AnnotationIntrospector introspector = new JacksonAnnotationIntrospector();

    mapper.getDeserializationConfig().withAnnotationIntrospector(introspector);

    mapper.getSerializationConfig().withAnnotationIntrospector(introspector);
    try
    {
      this.targetclass = (T) mapper.readValue(responseXML,  new TypeReference<ArrayList<T>>() {});
    }
    catch (JsonParseException e)
    {
      e.printStackTrace();
    }
    catch (JsonMappingException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }

    return this.targetclass;
}

Eu finalmente resolvi o problema. Consigo converter a Lista em String Json diretamente para ArrayList da seguinte maneira:

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
2

Isso funciona para mim.

@Test
public void cloneTest() {
    List<Part> parts = new ArrayList<Part>();
    Part part1 = new Part(1);
    parts.add(part1);
    Part part2 = new Part(2);
    parts.add(part2);
    try {
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonStr = objectMapper.writeValueAsString(parts);

        List<Part> cloneParts = objectMapper.readValue(jsonStr, new TypeReference<ArrayList<Part>>() {});
    } catch (Exception e) {
        //fail("failed.");
        e.printStackTrace();
    }

    //TODO: Assert: compare both list values.
}
Jugal Panchal
fonte