Come posso convertire JSON in una HashMap usando Gson?


286

Sto richiedendo dati da un server che restituisce dati nel formato JSON. Trasmettere una HashMap in JSON quando si effettua la richiesta non è stato affatto difficile, ma dall'altra parte sembra essere un po 'complicato. La risposta JSON è simile alla seguente:

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

Quale modo sarebbe più semplice accedere a questi dati? Sto usando il modulo GSON.


23
Map<String,Object> result = new Gson().fromJson(json, Map.class);funziona con gson 2.6.2.
Ferran Maylinch,

1
come riconvertire? Intendo dalla mappa a Json Array.
K.Sopheak, il

Risposte:


471

Ecco qui:

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

6
Buono ma non mi piace usarlo TypeToken- ha un cast implicito all'interno.
AlikElzin-Kilaka,

Lanciando su Map <>, hai finito le mie ore di frustrazione!
Dexter,

1
È un json valido nell'esempio?
Evan Kairuz,

@EvanKairuz No, non lo è. Dovrebbe essere{"k1":"apple","k2":"orange"}
Vadim Kotov,

e se avessi bisogno di convertire una stringa che in realtà è un array
Ravi Yadav il

111

Questo codice funziona:

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

1
Questo convertirà gli in in float prima che li trasformi in stringhe, ma funzionerà per convertire JSON in mappe a scopo di confronto.
louielouie,

12
funziona benissimo per me, ma ho cambiato la mappa in Map<String, Object>perché se il json non è solo stringhe si ottiene un errore
Moshe Shaham

5
Questo dà l'impressione sbagliata. La soluzione corretta per i tipi con parametri è TypeToken.
Sotirios Delimanolis,

Questa sarebbe una soluzione generica per tutti i tipi, ma un po 'insolita.
Leon,

76

So che questa è una domanda abbastanza vecchia, ma stavo cercando una soluzione per deserializzare genericamente JSON nidificato in un Map<String, Object> e non trovato nulla.

Nel modo in cui funziona il mio deserializzatore yaml, per impostazione predefinita vengono impostati gli oggetti JSON Map<String, Object>quando non si specifica un tipo, ma gson non sembra farlo. Fortunatamente puoi realizzarlo con un deserializzatore personalizzato.

Ho usato il seguente deserializzatore al naturalmente deserializzare nulla, inadempiente JsonObjects per Map<String, Object>e JsonArrays per Object[]s, dove tutti i bambini sono in modo simile deserializzati.

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

Il disordine all'interno del handlePrimitivemetodo è quello di assicurarti di ottenere sempre solo un Double, un Integer o un Long, e probabilmente potrebbe essere migliore, o almeno semplificato se stai bene con BigDecimals, che credo sia l'impostazione predefinita.

È possibile registrare questo adattatore come:

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

E poi chiamalo come:

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

Non sono sicuro del perché questo non sia il comportamento predefinito in gson, poiché è presente nella maggior parte delle altre librerie di serializzazione semi-strutturate ...


1
... anche se non sono del tutto sicuro di cosa fare ora con gli oggetti che torno. Non riesco a lanciarli come String anche se so che sono stringhe
Matt Zukowski

1
Aha! Il trucco era la chiamata ricorsivamente il deserializer invece della chiamata context.deserialize ().
Matt Zukowski

1
Avresti un po 'di codice Matt? Sto cercando di apportare le modifiche al deserializzatore ma non riesco davvero a capire il tuo punto
Romain Piel,

5
Gson ora per impostazione predefinita sembra avere il comportamento che Kevin Dolan sta assumendo nel suo frammento di codice.
eleotlecram,

1
@SomeoneSomewhere vedi risposta accettata qui stackoverflow.com/questions/14944419/gson-to-hashmap
MY

32

Con Gson 2.7 di Google (probabilmente anche versioni precedenti, ma ho provato con l'attuale versione 2.7) è semplice come:

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

Che restituisce un Maptipocom.google.gson.internal.LinkedTreeMap e funziona in modo ricorsivo su oggetti nidificati, array, ecc.

Ho eseguito l'esempio OP in questo modo (semplicemente sostituito double- con virgolette singole e spazio vuoto rimosso):

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

E ottenuto il seguente output:

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}

25

Aggiornamento per la nuova libreria Gson:
ora è possibile analizzare direttamente Json nidificato su Mappa, ma è necessario essere consapevoli nel caso in cui si tenta di analizzare Json per Map<String, Object>digitare: genererà un'eccezione. Per risolvere questo problema, basta dichiarare il risultato come LinkedTreeMaptipo. Esempio sotto:

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

Da dove importare LinkedTreeMap? Non riesco a trovarlo nel codice Gson.
HelpMeStackOverflowMyOnlyHope

Come ricordo, LinkedTreeMap è definito nella nuova libreria Gson. Puoi controllare qui: code.google.com/p/google-gson/source/browse/trunk/gson/src/main/…
Hoang Nguyen Huu

1
Per me funziona anche con Map<String,Object> result = gson.fromJson(json , Map.class);. Usare gson 2.6.2.
Ferran Maylinch,

12

Ho avuto la stessa identica domanda e sono finito qui. Ho avuto un approccio diverso che sembra molto più semplice (forse versioni più recenti di gson?).

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

con il seguente json

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

Il seguente

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

uscite

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

È possibile verificare dinamicamente l'utilizzo di instanceof durante la navigazione di jsonObject. Qualcosa di simile a

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

Funziona per me, quindi deve funzionare per te ;-)


6

Di seguito è supportato da 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

Prova questo, funzionerà. L'ho usato per 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;
}

Sostituisci KioskStatusResource nella tua classe e Integer nella tua classe chiave.


Questo ha funzionato per me dopo che HashMap ha lanciato un'eccezione LinkedTreeMap.
newfivefour

2

Ecco cosa ho usato:

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

Ecco un one-liner che lo farà:

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

sì, è una riga, ma tieni presente che new TypeToken<HashMap<String, Object>>(){}creerà una nuova sottoclasse in linea e tutte le
linter emetteranno

1

Ho superato un problema simile con un JsonDeSerializer personalizzato. Ho provato a renderlo un po 'generico ma ancora non abbastanza. È comunque una soluzione adatta alle mie esigenze.

Prima di tutto devi implementare un nuovo JsonDeserializer per gli oggetti Mappa.

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

E il metodo di deserializzazione sarà simile a questo:

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'aspetto negativo di questa soluzione è che la chiave della mia mappa è sempre di tipo "String". Tuttavia, cambiando alcune cose qualcuno può renderlo generico. Inoltre, devo dire che la classe del valore deve essere passata nel costruttore. Quindi il metodo getMyType()nel mio codice restituisce il tipo di valori della mappa, che è stato passato nel costruttore.

Puoi fare riferimento a questo post Come posso scrivere un deserializzatore JSON personalizzato per Gson? per saperne di più sui deserializzatori personalizzati.


1

Puoi invece usare questa classe :) (gestisce elenchi pari, elenchi nidificati e 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;
    }
}

Per convertire la tua stringa JSON in hashmap usa questo:

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

0

Questo è più un addendum alla risposta di Kevin Dolan che una risposta completa, ma ho avuto problemi ad estrarre il tipo dal Numero. Questa è la mia soluzione:

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

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

}

-3

JSONObject in genere utilizza HashMapinternamente per archiviare i dati. Quindi, puoi usarlo come Mappa nel tuo codice.

Esempio,

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

12
Questo proviene da json-lib, non da gson!
Ammar,

-3

Ho usato questo codice:

Gson gson = new Gson();
HashMap<String, Object> fields = gson.fromJson(json, HashMap.class);

Questo mi dà un avviso di conversione non selezionato.
Linea
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.