¿Cómo puedo convertir JSON a un HashMap usando Gson?

286

Estoy solicitando datos de un servidor que devuelve datos en formato JSON. Lanzar un HashMap en JSON al hacer la solicitud no fue difícil en absoluto, pero la otra forma parece ser un poco complicada. La respuesta JSON se ve así:

{ 
    "header" : { 
        "alerts" : [ 
            {
                "AlertID" : "2",
                "TSExpires" : null,
                "Target" : "1",
                "Text" : "woot",
                "Type" : "1"
            },
            { 
                "AlertID" : "3",
                "TSExpires" : null,
                "Target" : "1",
                "Text" : "woot",
                "Type" : "1"
            }
        ],
        "session" : "0bc8d0835f93ac3ebbf11560b2c5be9a"
    },
    "result" : "4be26bc400d3c"
}

¿De qué manera sería más fácil acceder a estos datos? Estoy usando el módulo GSON.

Mridang Agarwalla
fuente
23
Map<String,Object> result = new Gson().fromJson(json, Map.class);funciona con gson 2.6.2.
Ferran Maylinch
1
¿Cómo convertir de nuevo? Me refiero de Map a Json Array.
K.Sopheak

Respuestas:

471

Aqui tienes:

import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;

Type type = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> myMap = gson.fromJson("{'k1':'apple','k2':'orange'}", type);
heredar
fuente
66
Bueno, pero no me gusta usarlo TypeToken: hace un casting implícito dentro.
AlikElzin-kilaka
Transmitiendo al Mapa <>, ¡terminaste mis horas de frustración!
Dexter
1
¿Es ese json válido en el ejemplo?
Evan Kairuz
@EvanKairuz No, no lo es. Debería ser{"k1":"apple","k2":"orange"}
Vadim Kotov
¿Qué pasa si necesito convertir una cadena que en realidad es una matriz
Ravi Yadav
111

Este código funciona:

Gson gson = new Gson(); 
String json = "{\"k1\":\"v1\",\"k2\":\"v2\"}";
Map<String,Object> map = new HashMap<String,Object>();
map = (Map<String,Object>) gson.fromJson(json, map.getClass());
Ángel
fuente
1
Esto convertirá ints en flotantes antes de convertirlos en cadenas, pero funcionará para convertir JSON en mapas para fines de comparación.
louielouie
12
funciona muy bien para mí, pero cambié el mapa a Map<String, Object>porque si el json no es solo cadenas, obtienes un error
Moshe Shaham
55
Esto da la impresión equivocada. La solución correcta para los tipos parametrizados es TypeToken.
Sotirios Delimanolis
Esta sería una solución genérica para todos los tipos, pero un poco poco común.
Leon
76

Sé que esta es una pregunta bastante antigua, pero estaba buscando una solución para deserializar genéricamente JSON anidado a a Map<String, Object>, y no encontré nada.

De la forma en que funciona mi deserializador yaml, el valor predeterminado de los objetos JSON es Map<String, Object>cuando no especifica un tipo, pero gson no parece hacer esto. Afortunadamente, puedes lograrlo con un deserializador personalizado.

He utilizado el siguiente deserializer de forma natural deserializar nada, el impago JsonObjects a Map<String, Object>y JsonArrays a Object[]s, donde todos los niños se deserializan de manera similar.

private static class NaturalDeserializer implements JsonDeserializer<Object> {
  public Object deserialize(JsonElement json, Type typeOfT, 
      JsonDeserializationContext context) {
    if(json.isJsonNull()) return null;
    else if(json.isJsonPrimitive()) return handlePrimitive(json.getAsJsonPrimitive());
    else if(json.isJsonArray()) return handleArray(json.getAsJsonArray(), context);
    else return handleObject(json.getAsJsonObject(), context);
  }
  private Object handlePrimitive(JsonPrimitive json) {
    if(json.isBoolean())
      return json.getAsBoolean();
    else if(json.isString())
      return json.getAsString();
    else {
      BigDecimal bigDec = json.getAsBigDecimal();
      // Find out if it is an int type
      try {
        bigDec.toBigIntegerExact();
        try { return bigDec.intValueExact(); }
        catch(ArithmeticException e) {}
        return bigDec.longValue();
      } catch(ArithmeticException e) {}
      // Just return it as a double
      return bigDec.doubleValue();
    }
  }
  private Object handleArray(JsonArray json, JsonDeserializationContext context) {
    Object[] array = new Object[json.size()];
    for(int i = 0; i < array.length; i++)
      array[i] = context.deserialize(json.get(i), Object.class);
    return array;
  }
  private Object handleObject(JsonObject json, JsonDeserializationContext context) {
    Map<String, Object> map = new HashMap<String, Object>();
    for(Map.Entry<String, JsonElement> entry : json.entrySet())
      map.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class));
    return map;
  }
}

El desorden dentro del handlePrimitivemétodo es para asegurarse de que solo obtenga un Doble o un Entero o un Largo, y probablemente podría ser mejor, o al menos simplificado si está de acuerdo con obtener BigDecimals, que creo que es el valor predeterminado.

Puede registrar este adaptador como:

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());
Gson gson = gsonBuilder.create();

Y luego llámalo así:

Object natural = gson.fromJson(source, Object.class);

No estoy seguro de por qué este no es el comportamiento predeterminado en gson, ya que está en la mayoría de las otras bibliotecas de serialización semiestructurada ...

Kevin Dolan
fuente
1
... aunque no estoy muy seguro de qué hacer ahora con los Objetos que recupero. Parece que no puedo interpretarlos como String aunque sé que son strings
Matt Zukowski
1
¡Ajá! El truco era la llamada al deserializador recursivamente en lugar de la llamada context.deserialize ().
Matt Zukowski
1
¿Tendrías algún código, Matt? Estoy tratando de hacer los cambios en el deserializador pero realmente no puedo ver tu punto
Romain Piel
55
Gson ahora por defecto parece tener el comportamiento que Kevin Dolan busca en su fragmento de código.
Eleotlecram
1
@SomeoneSomewhere ver la respuesta aceptada aquí stackoverflow.com/questions/14944419/gson-to-hashmap
MI
32

Con Google's Gson 2.7 (probablemente también versiones anteriores, pero probé con la versión actual 2.7) es tan simple como:

Map map = gson.fromJson(jsonString, Map.class);

Que devuelve un Maptipo com.google.gson.internal.LinkedTreeMapy funciona de forma recursiva en objetos anidados, matrices, etc.

Ejecuté el ejemplo de OP de esta manera (simplemente reemplacé el doble con comillas simples y eliminé el espacio en blanco):

String jsonString = "{'header': {'alerts': [{'AlertID': '2', 'TSExpires': null, 'Target': '1', 'Text': 'woot', 'Type': '1'}, {'AlertID': '3', 'TSExpires': null, 'Target': '1', 'Text': 'woot', 'Type': '1'}], 'session': '0bc8d0835f93ac3ebbf11560b2c5be9a'}, 'result': '4be26bc400d3c'}";
Map map = gson.fromJson(jsonString, Map.class);
System.out.println(map.getClass().toString());
System.out.println(map);

Y obtuvo el siguiente resultado:

class com.google.gson.internal.LinkedTreeMap
{header={alerts=[{AlertID=2, TSExpires=null, Target=1, Text=woot, Type=1}, {AlertID=3, TSExpires=null, Target=1, Text=woot, Type=1}], session=0bc8d0835f93ac3ebbf11560b2c5be9a}, result=4be26bc400d3c}
isapir
fuente
25

Actualización para la nueva lib de Gson:
ahora puede analizar Json anidado a Map directamente, pero debe tener en cuenta en caso de que intente analizar Json para Map<String, Object>escribir: generará una excepción. Para solucionar esto, simplemente declare el resultado como LinkedTreeMaptipo. Ejemplo a continuación:

String nestedJSON = "{"id":"1","message":"web_didload","content":{"success":1}};
Gson gson = new Gson();
LinkedTreeMap result = gson.fromJson(nestedJSON , LinkedTreeMap.class);
Hoang Nguyen Huu
fuente
¿Desde dónde importo LinkedTreeMap? No puedo encontrarlo en el código Gson.
HelpMeStackOverflowMyOnlyHope
Como recuerdo, LinkedTreeMap se define en la nueva lib de Gson. Puede consultar aquí: code.google.com/p/google-gson/source/browse/trunk/gson/src/main/…
Hoang Nguyen Huu
1
Para mí también funciona con Map<String,Object> result = gson.fromJson(json , Map.class);. Usando gson 2.6.2.
Ferran Maylinch
12

Tenía exactamente la misma pregunta y terminé aquí. Tenía un enfoque diferente que parece mucho más simple (¿quizás versiones más nuevas de gson?).

    Gson gson = new Gson();
    Map jsonObject = (Map) gson.fromJson(data, Object.class);

con el siguiente json

{
  "map-00": {
    "array-00": [
      "entry-00",
      "entry-01"
     ],
     "value": "entry-02"
   }
}

El seguimiento

    Map map00 = (Map) jsonObject.get("map-00");
    List array00 = (List) map00.get("array-00");
    String value = (String) map00.get("value");
    for (int i = 0; i < array00.size(); i++) {
        System.out.println("map-00.array-00[" + i + "]= " + array00.get(i));
    }
    System.out.println("map-00.value = " + value);

salidas

map-00.array-00[0]= entry-00
map-00.array-00[1]= entry-01
map-00.value = entry-02

Puede verificar dinámicamente usando instanceof cuando navega por su jsonObject. Algo como

Map json = gson.fromJson(data, Object.class);
if(json.get("field") instanceof Map) {
  Map field = (Map)json.get("field");
} else if (json.get("field") instanceof List) {
  List field = (List)json.get("field");
} ...

Funciona para mí, por lo que debe funcionar para usted ;-)

krico
fuente
6

A continuación se admite desde gson 2.8.0

public static Type getMapType(Class keyType, Class valueType){
    return TypeToken.getParameterized(HashMap.class, keyType, valueType).getType();
}

public static  <K,V> HashMap<K,V> fromMap(String json, Class<K> keyType, Class<V> valueType){
    return gson.fromJson(json, getMapType(keyType,valueType));
}
León
fuente
3

Prueba esto, funcionará. Lo usé para Hashtable .

public static Hashtable<Integer, KioskStatusResource> parseModifued(String json) {
    JsonObject object = (JsonObject) new com.google.gson.JsonParser().parse(json);
    Set<Map.Entry<String, JsonElement>> set = object.entrySet();
    Iterator<Map.Entry<String, JsonElement>> iterator = set.iterator();

    Hashtable<Integer, KioskStatusResource> map = new Hashtable<Integer, KioskStatusResource>();

    while (iterator.hasNext()) {
        Map.Entry<String, JsonElement> entry = iterator.next();

        Integer key = Integer.parseInt(entry.getKey());
        KioskStatusResource value = new Gson().fromJson(entry.getValue(), KioskStatusResource.class);

        if (value != null) {
            map.put(key, value);
        }

    }
    return map;
}

Reemplace KioskStatusResource a su clase e Integer a su clase clave.

R4U
fuente
Esto funcionó para mí después de que HashMap lanzó una excepción LinkedTreeMap.
newfivefour
2

Esto es lo que he estado usando:

public static HashMap<String, Object> parse(String json) {
    JsonObject object = (JsonObject) parser.parse(json);
    Set<Map.Entry<String, JsonElement>> set = object.entrySet();
    Iterator<Map.Entry<String, JsonElement>> iterator = set.iterator();
    HashMap<String, Object> map = new HashMap<String, Object>();
    while (iterator.hasNext()) {
        Map.Entry<String, JsonElement> entry = iterator.next();
        String key = entry.getKey();
        JsonElement value = entry.getValue();
        if (!value.isJsonPrimitive()) {
            map.put(key, parse(value.toString()));
        } else {
            map.put(key, value.getAsString());
        }
    }
    return map;
}
OZG
fuente
2

Aquí hay una frase que lo hará:

HashMap<String, Object> myMap =
   gson.fromJson(yourJson, new TypeToken<HashMap<String, Object>>(){}.getType());
Ab ِ
fuente
sí, es una línea, pero tenga en cuenta que new TypeToken<HashMap<String, Object>>(){}creará una nueva subclase en línea, y todas las listas generarán una advertencia al menos, supongo
Martin Meeser
1

He superado un problema similar con un JsonDeSerializer personalizado. Traté de hacerlo un poco genérico pero aún no lo suficiente. Sin embargo, es una solución que se adapta a mis necesidades.

En primer lugar, debe implementar un nuevo JsonDeserializer para objetos Map.

public class MapDeserializer<T, U> implements JsonDeserializer<Map<T, U>>

Y el método de deserialización se verá similar a esto:

public Map<T, U> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
        throws JsonParseException {

        if (!json.isJsonObject()) {
            return null;
        }

        JsonObject jsonObject = json.getAsJsonObject();
        Set<Entry<String, JsonElement>> jsonEntrySet = jsonObject.entrySet();
        Map<T, U> deserializedMap = new HashMap<T, U>();

        for (Entry<java.lang.String, JsonElement> entry : jsonEntrySet) {
            try {
                U value = context.deserialize(entry.getValue(), getMyType());
                deserializedMap.put((T) entry.getKey(), value);
            } catch (Exception ex) {
                logger.info("Could not deserialize map.", ex);
            }
        }

        return deserializedMap;
    }

La desventaja de esta solución es que la clave de mi Mapa siempre es de Tipo "Cadena". Sin embargo, al cambiar algunas cosas, alguien puede hacerlo genérico. Además, debo decir que la clase del valor debe pasarse en el constructor. Entonces, el método getMyType()en mi código devuelve el tipo de valores del Mapa, que se pasó en el constructor.

Puedes hacer referencia a esta publicación ¿Cómo escribo un deserializador JSON personalizado para Gson? para aprender más sobre deserializadores personalizados.

nikkatsa
fuente
1

Puede usar esta clase en su lugar :) (maneja incluso listas, listas anidadas y json)

public class Utility {

    public static Map<String, Object> jsonToMap(Object json) throws JSONException {

        if(json instanceof JSONObject)
            return _jsonToMap_((JSONObject)json) ;

        else if (json instanceof String)
        {
            JSONObject jsonObject = new JSONObject((String)json) ;
            return _jsonToMap_(jsonObject) ;
        }
        return null ;
    }


   private static Map<String, Object> _jsonToMap_(JSONObject json) throws JSONException {
        Map<String, Object> retMap = new HashMap<String, Object>();

        if(json != JSONObject.NULL) {
            retMap = toMap(json);
        }
        return retMap;
    }


    private static Map<String, Object> toMap(JSONObject object) throws JSONException {
        Map<String, Object> map = new HashMap<String, Object>();

        Iterator<String> keysItr = object.keys();
        while(keysItr.hasNext()) {
            String key = keysItr.next();
            Object value = object.get(key);

            if(value instanceof JSONArray) {
                value = toList((JSONArray) value);
            }

            else if(value instanceof JSONObject) {
                value = toMap((JSONObject) value);
            }
            map.put(key, value);
        }
        return map;
    }


    public static List<Object> toList(JSONArray array) throws JSONException {
        List<Object> list = new ArrayList<Object>();
        for(int i = 0; i < array.length(); i++) {
            Object value = array.get(i);
            if(value instanceof JSONArray) {
                value = toList((JSONArray) value);
            }

            else if(value instanceof JSONObject) {
                value = toMap((JSONObject) value);
            }
            list.add(value);
        }
        return list;
    }
}

Para convertir su cadena JSON a hashmap, use esto:

HashMap<String, Object> hashMap = new HashMap<>(Utility.jsonToMap(response)) ;
Natesh Bhat
fuente
0

Esto es más una adición a la respuesta de Kevin Dolan que una respuesta completa, pero estaba teniendo problemas para extraer el tipo del Número. Esta es mi solución:

private Object handlePrimitive(JsonPrimitive json) {
  if(json.isBoolean()) {
    return json.getAsBoolean();
  } else if(json.isString())
    return json.getAsString();
  }

  Number num = element.getAsNumber();

  if(num instanceof Integer){
    map.put(fieldName, num.intValue());
  } else if(num instanceof Long){
    map.put(fieldName, num.longValue());
  } else if(num instanceof Float){
    map.put(fieldName, num.floatValue());
  } else {    // Double
     map.put(fieldName, num.doubleValue());
  }
}
Luke
fuente
-1
 HashMap<String, String> jsonToMap(String JsonDetectionString) throws JSONException {

    HashMap<String, String> map = new HashMap<String, String>();
    Gson gson = new Gson();

    map = (HashMap<String, String>) gson.fromJson(JsonDetectionString, map.getClass());

    return map;

}
usuario2918406
fuente
-3

JSONObject generalmente usa HashMapinternamente para almacenar los datos. Por lo tanto, puede usarlo como Mapa en su código.

Ejemplo,

JSONObject obj = JSONObject.fromObject(strRepresentation);
Iterator i = obj.entrySet().iterator();
while (i.hasNext()) {
   Map.Entry e = (Map.Entry)i.next();
   System.out.println("Key: " + e.getKey());
   System.out.println("Value: " + e.getValue());
}
Phanindra
fuente
12
¡Esto es de json-lib, no de gson!
Ammar
-3

Usé este código:

Gson gson = new Gson();
HashMap<String, Object> fields = gson.fromJson(json, HashMap.class);
mortalis
fuente
Esto me da una advertencia de conversión sin marcar.
Línea