How can I convert JSON to a HashMap using Gson?

364,729

Solution 1

Here you go:

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

Solution 2

This code works:

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

Solution 3

I know this is a fairly old question, but I was searching for a solution to generically deserialize nested JSON to a Map<String, Object>, and found nothing.

The way my yaml deserializer works, it defaults JSON objects to Map<String, Object> when you don't specify a type, but gson doesn't seem to do this. Luckily you can accomplish it with a custom deserializer.

I used the following deserializer to naturally deserialize anything, defaulting JsonObjects to Map<String, Object> and JsonArrays to Object[]s, where all the children are similarly deserialized.

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

The messiness inside the handlePrimitive method is for making sure you only ever get a Double or an Integer or a Long, and probably could be better, or at least simplified if you're okay with getting BigDecimals, which I believe is the default.

You can register this adapter like:

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

And then call it like:

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

I'm not sure why this is not the default behavior in gson, since it is in most other semi-structured serialization libraries...

Solution 4

With google's Gson 2.7 (probably earlier versions too, but I tested with the current version 2.7) it's as simple as:

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

Which returns a Map of type com.google.gson.internal.LinkedTreeMap and works recursively on nested objects, arrays, etc.

I ran the OP example like so (simply replaced double- with single-quotes and removed whitespace):

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

And got the following 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}

Solution 5

Update for new Gson lib:
You now can parse nested Json to Map directly, but you should be aware in case you try to parse Json to Map<String, Object> type: it will raise exception. To fix this, just declare the result as LinkedTreeMap type. Example below:

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

Related videos on Youtube

Mridang Agarwalla
Author by

Mridang Agarwalla

I'm a software developer who relishes authoring Java and Python, hacking on Android and toying with AppEngine. I have a penchant for development and a passion for the business side of software. In between all the work, I contribute to a number of open-source projects, learn to master the art of cooking Asian cuisine and try to stay sane while learning to fly my Align Trex-600 Nitro Heli.

Updated on March 28, 2022

Comments

  • Mridang Agarwalla
    Mridang Agarwalla over 2 years

    I'm requesting data from a server which returns data in the JSON format. Casting a HashMap into JSON when making the request wasn't hard at all but the other way seems to be a little tricky. The JSON response looks like this:

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

    What way would be easiest to access this data? I'm using the GSON module.

    • Ferran Maylinch
      Ferran Maylinch over 8 years
      Map<String,Object> result = new Gson().fromJson(json, Map.class); works with gson 2.6.2.
  • Matt Zukowski
    Matt Zukowski about 13 years
    ... although I'm not quite sure what to do now with the Objects I get back. Can't seem to cast them as String even though I know they're strings
  • Matt Zukowski
    Matt Zukowski about 13 years
    Aha! The trick was the call the deserializer recursively instead of the context.deserialize() call.
  • Megasaur
    Megasaur about 13 years
    Thanks for that. It's a little strange that it does not do this my by itself.
  • Romain Piel
    Romain Piel almost 13 years
    Would you have some code Matt? I'm trying to make the changes on the deserializer but I can't really see your point
  • Ammar
    Ammar over 12 years
    This is from json-lib, not gson!
  • louielouie
    louielouie over 11 years
    This will convert ints to floats before it turns them into strings, but it will work to convert JSON into maps for comparison purposes.
  • eleotlecram
    eleotlecram over 11 years
    @kirhgoff: It works for me, but you have to do what Matt Zukowski says: call the deserializer recursively.
  • eleotlecram
    eleotlecram over 11 years
    @Romain Piel: You have to make two changes to Kevin's code to make it recursively: 1) array[i] = context.deserialize(json.get(i), Object.class); --> array[i] = this.deserialize(json.get(i), Object.class, context); 2) map.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class)); --> map.put(entry.getKey(), this.deserialize(entry.getValue(), Object.class, context));
  • eleotlecram
    eleotlecram over 11 years
    Gson now by default appears to have the behavior that Kevin Dolan is going for in his code snippet.
  • Moshe Shaham
    Moshe Shaham almost 11 years
    works great for me, but I changed the map to Map<String, Object> because if the json is not only strings you get an error
  • Someone Somewhere
    Someone Somewhere over 10 years
    Using this I still get Double's instead of Integer's when converting to Object's (using a gson library > 1.7). Disappointed.
  • mmm111mmm
    mmm111mmm over 10 years
    This worked for me after HashMap threw an LinkedTreeMap exception.
  • AlikElzin-kilaka
    AlikElzin-kilaka about 10 years
    Good one but I don't like using TypeToken - it does implicit casting inside.
  • M.Y.
    M.Y. almost 10 years
    @SomeoneSomewhere see accepted answer here stackoverflow.com/questions/14944419/gson-to-hashmap
  • HelpMeStackOverflowMyOnlyHope
    HelpMeStackOverflowMyOnlyHope almost 10 years
    Where do I import LinkedTreeMap from? I can't find it in the Gson code.
  • Hoang Nguyen Huu
    Hoang Nguyen Huu about 9 years
    As I remember, LinkedTreeMap is defined in new Gson lib. You can check here: code.google.com/p/google-gson/source/browse/trunk/gson/src/m‌​ain/…
  • Ferran Maylinch
    Ferran Maylinch over 8 years
    For me it works also with Map<String,Object> result = gson.fromJson(json , Map.class);. Using gson 2.6.2.
  • Sotirios Delimanolis
    Sotirios Delimanolis almost 8 years
    This gives the wrong impression. The correct solution for parameterized types is TypeToken.
  • Andy
    Andy over 7 years
    @SomeoneSomewhere I tried this code with the latest Gson and it doesn't even call NaturalDeserializer, that's why. I think it just calls its default handler for Object.
  • Dexter
    Dexter over 7 years
    Casting to Map<>, you ended my hours of frustration !
  • Line
    Line about 7 years
    This gives me unchecked conversion warning.
  • Leon
    Leon almost 6 years
    This would be a generic solution for all types, but a little bit uncommon.
  • Evan Kairuz
    Evan Kairuz over 5 years
    Is that valid json in the example?
  • Vadim Kotov
    Vadim Kotov almost 5 years
    @EvanKairuz No, it is not. It should be {"k1":"apple","k2":"orange"}
  • Ravi Yadav
    Ravi Yadav over 4 years
    what if I need to convert string which is actually a array
  • Martin Meeser
    Martin Meeser about 4 years
    yes it is one line but keep in mind that new TypeToken<HashMap<String, Object>>(){} will create a new inline sub-class, and all linters will raise a warning at least I guess
  • roottraveller
    roottraveller almost 4 years
    new Gson().fromJson(jsonData, new TypeToken<Map<String, Integer>>(){}.getType()); is converting to Double not Integer??
  • gcr
    gcr about 3 years
    For me what worked (thanks to advice above!) is convert nested HashMap<String, Object> (because the TypeToken trick didn't work for me on nested) was to return them just as LinkedTreeMap objects. From there I just iterated over the LinkedTreeMap keys and populated new HashMaps in the loop, as they have the same methods. Don't know why you can't straight cast but met my level of need.
  • user666
    user666 about 3 years
    what if the json contains nested objects or lists? how can we have them in the map without having to deal with maps as values?
  • Ferran Maylinch
    Ferran Maylinch about 3 years
    This solution has caused me problems with nested objects, so I finally used new Gson().fromJson(json, Map.class);