Jackson como transformar JsonNode em ArrayNode sem lançar?

116

Estou mudando minha biblioteca JSON de org.json para Jackson e quero migrar o seguinte código:

JSONObject datasets = readJSON(new URL(DATASETS));
JSONArray datasetArray =  datasets.getJSONArray("datasets");

Agora em Jackson eu tenho o seguinte:

ObjectMapper m = new ObjectMapper();
JsonNode datasets = m.readTree(new URL(DATASETS));      
ArrayNode datasetArray = (ArrayNode)datasets.get("datasets");

Porém não gosto do elenco aí, existe a possibilidade de um ClassCastException? Existe um método equivalente a getJSONArrayin org.jsonpara que eu tenha o tratamento de erros adequado caso não seja um array?

Konrad Höffner
fonte
Infelizmente, não posso usar o mapeamento completo porque os dados não corrigem nomes de campo.
Konrad Höffner
1
Se os nomes dos campos vierem de um conjunto limitado, você pode definir uma classe com todos eles e usar o FAIL_ON_UNKNOWN_PROPERTIESrecurso do desserializador para obter apenas valores nulos retornados nos campos não utilizados. Mas é claro que isso é apenas uma opção se o conjunto de nomes de campo for relativamente limitado.
fvu
Hm, acho que essa solução não se encaixa melhor no meu caso, mas vou me lembrar dela no caso de ter um problema com um conjunto limitado que é conhecido de antemão!
Konrad Höffner

Respostas:

247

Sim, o design do analisador manual de Jackson é bastante diferente de outras bibliotecas. Em particular, você notará que JsonNodepossui a maioria das funções que você normalmente associa a nós de array de outras APIs. Como tal, você não precisa lançar para um ArrayNodepara usar. Aqui está um exemplo:

JSON:

{
    "objects" : ["One", "Two", "Three"]
}

Código:

final String json = "{\"objects\" : [\"One\", \"Two\", \"Three\"]}";

final JsonNode arrNode = new ObjectMapper().readTree(json).get("objects");
if (arrNode.isArray()) {
    for (final JsonNode objNode : arrNode) {
        System.out.println(objNode);
    }
}

Resultado:

"Um"
"Dois"
"Três"

Observe o uso de isArraypara verificar se o nó é realmente uma matriz antes de iterar. A verificação não é necessária se você estiver absolutamente confiante em sua estrutura de dados, mas ela estará disponível caso você precise (e isso não é diferente da maioria das outras bibliotecas JSON).

Percepção
fonte
2
Você me salvou horas. Obrigado!
Igor Morais
Posso saber por que "final" é usado na linha "for (final JsonNode objNode: arrNode)"?
Anthony Vinay
5

No Java 8, você pode fazer assim:

import java.util.*;
import java.util.stream.*;

List<JsonNode> datasets = StreamSupport
    .stream(datasets.get("datasets").spliterator(), false)
    .collect(Collectors.toList())
Ori Popowski
fonte
1

Existe um método equivalente a getJSONArray em org.json para que eu tenha o tratamento de erros adequado caso não seja um array?

Depende de sua entrada; ou seja, as coisas que você busca no URL. Se o valor do atributo "datasets" for uma matriz associativa em vez de uma matriz simples, você obterá a ClassCastException.

Mas, novamente, a exatidão de sua versão antiga também depende da entrada. Na situação em que sua nova versão lançar um ClassCastException, a versão antiga lançará JSONException. Referência: http://www.json.org/javadoc/org/json/JSONObject.html#getJSONArray(java.lang.String)

Stephen C
fonte
Ok, então eu poderia pegar uma ClassCastException, obrigado! Para o meu gosto, é um pouco menos elegante do que ter um JsonException específico, mas se não for possível de outra forma, ainda é bom.
Konrad Höffner
0

Eu presumiria que, no final do dia, você deseja consumir os dados no ArrayNode iterando-o. Por isso:

Iterator<JsonNode> iterator = datasets.withArray("datasets").elements();
while (iterator.hasNext()) 
        System.out.print(iterator.next().toString() + " "); 

ou se você gosta de streams e funções lambda:

import com.google.common.collect.Streams;
Streams.stream(datasets.withArray("datasets").elements())
    .forEach( item -> System.out.print(item.toString()) )
Martelo Feroz
fonte