Converter JSON em um mapa usando o Gson

Converter JSON em um mapa usando o Gson

1. Introdução

In this quick tutorial, we’ll learn how to convert a JSON string to a Map using Gson from Google.

Veremos três abordagens diferentes para fazer isso e discutiremos seus prós e contras - com alguns exemplos práticos.

2. AprovandoMap.class

Em geral,Gson provides the following API in its Gson class to convert a JSON string to an object:

public  T fromJson(String json, Class classOfT) throws JsonSyntaxException;

A partir da assinatura, fica muito claro que o segundo parâmetro é a classe do objeto que pretendemos que o JSON analise. Em nosso caso, deve serMap.class:

String jsonString = "{'employee.name':'Bob','employee.salary':10000}";
Gson gson = new Gson();
Map map = gson.fromJson(jsonString, Map.class);
Assert.assertEquals(2, map.size());
Assert.assertEquals(Double.class, map.get("employee.salary").getClass());

Essa abordagem fará sua melhor estimativa em relação ao tipo de valor para cada propriedade.

Por exemplo, os números serão coagidos emDoubles,true areiafalse emBoolean e os objetos emLinkedTreeMaps.

Se houver chaves duplicadas, porém, a coerção falhará e lançará umJsonSyntaxException.

E, devido atype erasure,, também não poderemos configurar este comportamento de coerção. Portanto, se precisarmos especificar os tipos de chave ou valor, precisaremos de uma abordagem diferente.

3. UsandoTypeToken

To overcome the problem of type-erasure for the generic types, Gson has an overloaded version of the API:

public  T fromJson(String json, Type typeOfT) throws JsonSyntaxException;

Podemos construir umMap com seus parâmetros de tipo usandoTypeToken de Gson. The TypeToken class returns an instance of ParameterizedTypeImpl that preserves the type of the key and value even at runtime:

String jsonString = "{'Bob' : {'name': 'Bob Willis'},"
  + "'Jenny' : {'name': 'Jenny McCarthy'}, "
  + "'Steve' : {'name': 'Steven Waugh'}}";
Gson gson = new Gson();
Type empMapType = new TypeToken>() {}.getType();
Map nameEmployeeMap = gson.fromJson(jsonString, empMapType);
Assert.assertEquals(3, nameEmployeeMap.size());
Assert.assertEquals(Employee.class, nameEmployeeMap.get("Bob").getClass());

Agora, se construirmos nosso tipoMap comoMap<String, Object>, o analisador ainda será o padrão, como vimos na seção anterior.

Obviamente, isso ainda se aplica a Gson por coagir tipos primitivos. Esses, no entanto, também podem ser personalizados.

4. UsandoJsonDeserializer personalizado

Quando precisamos de controle refinado sobre a construção de nosso objetoMap, podemos implementar um desserializador personalizado do tipoJsonDeserializer<Map>.

Para ver um exemplo, vamos supor que nosso JSON contenha o nome do funcionário como chave e a data de contratação como valor. Além disso, vamos assumir que o formato da data éyyyy/MM/dd, que não é um formato padrão paraGson.

Podemos configurar o Gson para analisar nosso mapa de maneira diferente, então, implementando umJsonDeserializer:

public class StringDateMapDeserializer implements JsonDeserializer> {

    private SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");

    @Override
    public Map deserialize(JsonElement elem,
          Type type,
          JsonDeserializationContext jsonDeserializationContext) {
        return elem.getAsJsonObject()
          .entrySet()
          .stream()
          .filter(e -> e.getValue().isJsonPrimitive())
          .filter(e -> e.getValue().getAsJsonPrimitive().isString())
          .collect(
            Collectors.toMap(
              Map.Entry::getKey,
              e -> formatDate(e.getValue())));
    }

    private Date formatDate(Object value) {
        try {
            return format(value.getAsString());
        } catch (ParseException ex) {
            throw new JsonParseException(ex);
        }
    }
}

Agora, temos que registrá-lo emGsonBuilder contra nosso tipo de destinoMap<String, Date> e construir um objetoGson personalizado.

Quando chamamos a APIfromJson neste objetoGson, o analisador invoca o desserializador personalizado e retorna a instânciaMap desejada:

String jsonString = "{'Bob': '2017-06-01', 'Jennie':'2015-01-03'}";
Type type = new TypeToken>(){}.getType();
Gson gson = new GsonBuilder()
  .registerTypeAdapter(type, new StringDateMapDeserializer())
  .create();
Map empJoiningDateMap = gson.fromJson(jsonString, type);
Assert.assertEquals(2, empJoiningDateMap.size());
Assert.assertEquals(Date.class, empJoiningDateMap.get("Bob").getClass());

Essa tática também é útil quando nosso mapa pode conter valores heterogêneos e temos uma boa idéia de quantos tipos diferentes de valores podem estar lá.

Para saber mais sobre um desserializador personalizado emGson, sinta-se à vontade para passar porGson Deserialization Cookbook.

5. Conclusão

Neste breve artigo, aprendemos várias maneiras de construir um mapa a partir de uma string formatada em JSON. E também discutimos casos de uso adequados para essas variações.

O código-fonte dos exemplos está disponívelover on GitHub.