Comment convertir JSON en HashMap en utilisant Gson?

286

Je demande des données à un serveur qui renvoie des données au format JSON. Transposer un HashMap en JSON lors de la demande n'a pas été difficile du tout, mais l'autre sens 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"
}

Quelle serait la manière la plus simple d'accéder à ces données? J'utilise le module GSON.

Mridang Agarwalla
la source
23
Map<String,Object> result = new Gson().fromJson(json, Map.class);fonctionne avec gson 2.6.2.
Ferran Maylinch
1
comment reconvertir? Je veux dire de Map à Json Array.
K.Sopheak

Réponses:

471

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);
cherit
la source
6
Bon, mais je n'aime pas l'utiliser TypeToken- cela implique un casting implicite à l'intérieur.
AlikElzin-kilaka
Casting sur la carte <>, vous avez terminé mes heures de frustration!
Dexter
1
Est-ce un json valide dans l'exemple?
Evan Kairuz
@EvanKairuz Non, ce n'est pas le cas. Ça devrait être{"k1":"apple","k2":"orange"}
Vadim Kotov
et si j'ai besoin de convertir une chaîne qui est en fait un tableau
Ravi Yadav
111

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());
ange
la source
1
Cela convertira les entiers en flottants avant de les transformer en chaînes, mais cela fonctionnera pour convertir JSON en cartes à des fins de comparaison.
louielouie
12
fonctionne très bien pour moi, mais j'ai changé la carte Map<String, Object>parce que si le json n'est pas seulement des cordes, vous obtenez une erreur
Moshe Shaham
5
Cela donne une mauvaise impression. La solution correcte pour les types paramétrés est TypeToken.
Sotirios Delimanolis
Ce serait une solution générique pour tous les types, mais un peu rare.
Leon
76

Je sais que c'est une question assez ancienne, mais je cherchais une solution pour désérialiser génériquement le JSON imbriqué en un Map<String, Object>, et je n'ai rien trouvé.

De la façon dont mon désérialiseur yaml fonctionne, il définit par défaut les objets JSON Map<String, Object>lorsque vous ne spécifiez pas de type, mais gson ne semble pas le faire. Heureusement, vous pouvez l'accomplir avec un désérialiseur personnalisé.

J'ai utilisé le désérialiseur suivant pour désérialiser naturellement tout, par défaut JsonObjects à Map<String, Object>et 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 à l'intérieur de la handlePrimitiveméthode est de s'assurer que vous n'obtenez jamais qu'un Double ou un Entier ou un Long, et pourrait probablement être mieux, ou au moins simplifié si vous êtes d'accord avec l'obtention de BigDecimals, qui je crois est la valeur par défaut.

Vous pouvez enregistrer cet adaptateur comme:

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

Et puis appelez-le comme:

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

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

Kevin Dolan
la source
1
... même si je ne sais pas trop quoi faire maintenant avec les objets que je récupère. Je n'arrive pas à les utiliser comme String même si je sais que ce sont des cordes
Matt Zukowski
1
Ah! L'astuce était l'appel récursif du désérialiseur au lieu de l'appel context.deserialize ().
Matt Zukowski
1
Auriez-vous du code Matt? J'essaie de faire les changements sur le désérialiseur mais je ne vois pas vraiment ce que vous voulez dire
Romain Piel
5
Par défaut, Gson semble avoir le comportement recherché par Kevin Dolan dans son extrait de code.
eleotlecram
1
@SomeoneSomewhere voir la réponse acceptée ici stackoverflow.com/questions/14944419/gson-to-hashmap
MON
32

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

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

Qui retourne un Maptype com.google.gson.internal.LinkedTreeMapet fonctionne récursivement sur les objets imbriqués, les tableaux, etc.

J'ai exécuté l'exemple OP comme ceci (simplement remplacé double- par des guillemets simples et supprimé les espaces blancs):

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}
isapir
la source
25

Mise à jour pour la nouvelle bibliothèque Gson:
Vous pouvez maintenant analyser directement Json imbriqué dans Map, mais vous devez être conscient au cas où vous essayez d'analyser Json pour Map<String, Object>taper: cela lèvera une exception. Pour résoudre ce problème, déclarez simplement le résultat comme LinkedTreeMaptype. Exemple ci-dessous:

String nestedJSON = "{"id":"1","message":"web_didload","content":{"success":1}};
Gson gson = new Gson();
LinkedTreeMap result = gson.fromJson(nestedJSON , LinkedTreeMap.class);
Hoang Nguyen Huu
la source
D'où puis-je importer LinkedTreeMap? Je ne le trouve pas dans le code Gson.
HelpMeStackOverflowMyOnlyHope
Si je me souviens bien, LinkedTreeMap est défini dans la nouvelle lib Gson. Vous pouvez vérifier ici: code.google.com/p/google-gson/source/browse/trunk/gson/src/main/…
Hoang Nguyen Huu
1
Pour moi ça marche aussi avec Map<String,Object> result = gson.fromJson(json , Map.class);. Utilisation de gson 2.6.2.
Ferran Maylinch
12

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 des versions plus récentes 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 dans 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 ;-)

krico
la source
6

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));
}
Léon
la source
3

Essayez ceci, cela fonctionnera. Je l'ai utilisé pour 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;
}

Remplacez KioskStatusResource dans votre classe et Integer dans votre classe de clés.

R4U
la source
Cela a fonctionné pour moi après que HashMap a levé une exception LinkedTreeMap.
newfivefour
2

Voici ce que j'utilise:

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
la source
2

Voici une doublure qui le fera:

HashMap<String, Object> myMap =
   gson.fromJson(yourJson, new TypeToken<HashMap<String, Object>>(){}.getType());
Un B
la source
oui, c'est une ligne, mais gardez à l'esprit que new TypeToken<HashMap<String, Object>>(){}cela créera une nouvelle sous-classe en ligne, et tous les
linters émettront
1

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 suffisant. C'est une solution qui correspond à 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;
    }

L'inconvénient de cette solution est que la clé de ma carte est toujours de 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. Ainsi, 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 cet article Comment écrire un désérialiseur JSON personnalisé pour Gson? afin d'en savoir plus sur les désérialiseurs personnalisés.

nikkatsa
la source
1

Vous pouvez utiliser cette classe à la place :) (gère les listes paires, 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)) ;
Natesh bhat
la source
0

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. Voici 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());
  }
}
Luc
la source
-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;

}
user2918406
la source
-3

JSONObject utilise généralement en HashMapinterne pour stocker les données. Vous pouvez donc l'utiliser comme carte dans votre code.

Exemple,

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
la source
12
C'est de json-lib, pas de gson!
Ammar
-3

J'ai utilisé ce code:

Gson gson = new Gson();
HashMap<String, Object> fields = gson.fromJson(json, HashMap.class);
mortalis
la source
Cela me donne un avertissement de conversion non contrôlé.
Ligne