web-dev-qa-db-fra.com

Comment puis-je convertir JSON en HashMap avec Gson?

Je demande des données à un serveur qui renvoie des données au format JSON. Utiliser une HashMap dans JSON lors de la création de la requête n'a pas été difficile, mais l'inverse semble être un peu délicat. La réponse JSON ressemble à ceci:

{ 
    "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"
}

Quel serait le moyen le plus facile d'accéder à ces données? J'utilise le module GSON.

246

Voici:

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);
405
cherit

Ce code fonctionne:

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());
100
Angel

Je sais que la question est assez ancienne, mais je recherchais une solution permettant de désérialiser de manière générique le code JSON imbriqué en un Map<String, Object>, sans rien trouver.

De la manière dont mon désérialisateur yaml fonctionne, les objets JSON par défaut sont définis sur Map<String, Object> lorsque vous ne spécifiez pas de type, mais que gson ne semble pas le faire. Heureusement, vous pouvez le réaliser avec un désérialiseur personnalisé.

J'ai utilisé le désérialiseur suivant pour désérialiser naturellement quoi que ce soit, par défaut de JsonObjects à Map<String, Object> et de JsonArrays à Object[]s, où tous les enfants sont désérialisés de la même manière.

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;
  }
}

Le désordre dans la méthode handlePrimitive est de s'assurer que vous n'obtenez jamais qu'un double, un entier ou un long, et pourrait probablement être meilleur, ou du moins simplifié si vous voulez bien avoir BigDecimals, ce qui, à mon avis, est le défaut.

Vous pouvez enregistrer cet adaptateur comme suit:

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

Et puis appelez comme ça:

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

Je ne sais pas pourquoi ce n'est pas le comportement par défaut de gson, car c'est le cas dans la plupart des autres bibliothèques de sérialisation semi-structurées ...

75
Kevin Dolan

Avec Gson 2.7 de Google (probablement aussi des versions antérieures, mais j'ai testé avec la version actuelle 2.7), c'est aussi simple que:

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

Ce qui retourne une Map de type com.google.gson.internal.LinkedTreeMap et fonctionne de manière récursive sur des objets imbriqués, des tableaux, etc.

J'ai exécuté l'exemple OP comme suit (simplement remplacé par des guillemets simples et des espaces supprimés):

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);

Et obtenu la sortie suivante:

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}
24
isapir

Mise à jour pour la nouvelle librairie Gson:
Vous pouvez maintenant analyser directement Json imbriqué sur Mapper, mais vous devez savoir si vous essayez d'analyser Json avec le type Map<String, Object>: il déclenchera une exception. Pour résoudre ce problème, déclarez simplement le résultat en tant que type LinkedTreeMap. Exemple ci-dessous:

String nestedJSON = "{"id":"1","message":"web_didload","content":{"success":1}};
Gson gson = new Gson();
LinkedTreeMap result = gson.fromJson(nestedJSON , LinkedTreeMap.class);
24
Hoang Huu

J'avais exactement la même question et je me suis retrouvé ici. J'ai eu une approche différente qui semble beaucoup plus simple (peut-être de nouvelles versions de gson?).

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

avec le json suivant 

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

Le suivant

    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);

les sorties

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

Vous pouvez vérifier dynamiquement en utilisant instanceof lors de la navigation de votre jsonObject. Quelque chose comme

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");
} ...

Cela fonctionne pour moi, donc cela doit fonctionner pour vous ;-)

11
krico

Essayez ceci, cela fonctionnera. Je l'ai utilisé pourHashtable.

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;
}

RemplacezKioskStatusResourcepar votre classe etIntegerpar votre classe de clés.

3
R4U

Ci-dessous est pris en charge depuis 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));
}
3
Leon

Voici ce que j'ai utilisé:

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;
}
2
OZG

Vous pouvez utiliser cette classe à la place :) (gère même les listes, les listes imbriquées et 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;
    }
}

Pour convertir votre chaîne JSON en hashmap utilisez ceci: 

HashMap<String, Object> hashMap = new HashMap<>(Utility.jsonToMap(response)) ;
1
Natesh bhat

J'ai surmonté un problème similaire avec un JsonDeSerializer personnalisé. J'ai essayé de le rendre un peu générique mais toujours pas assez. C'est une solution qui répond à mes besoins. 

Tout d'abord, vous devez implémenter un nouveau JsonDeserializer pour les objets Map.

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

Et la méthode de désérialisation ressemblera à ceci:

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;
    }

Le problème avec cette solution est que la clé de ma carte est toujours du type "String". Cependant, en changeant certaines choses, quelqu'un peut le rendre générique. De plus, je dois dire que la classe de la valeur doit être passée dans le constructeur. Donc, la méthode getMyType() dans mon code renvoie le type des valeurs de la carte, qui a été passé dans le constructeur.

Vous pouvez référencer ce post Comment puis-je écrire un désérialiseur JSON personnalisé pour Gson? pour en savoir plus sur les désérialiseurs personnalisés.

1
nikkatsa

Voici un one-liner qui le fera:

HashMap<String, Object> myMap =
   gson.fromJson(yourJson, new TypeToken<HashMap<String, Object>>(){}.getType());
1
Ab_

C’est plus un addendum à la réponse de Kevin Dolan qu’une réponse complète, mais j’avais du mal à extraire le type du numéro. Ceci est ma solution:

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());
  }
}
0
Luke Salamone