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
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;
}
Here's a one-liner that will do it:
HashMap<String, Object> myMap =
gson.fromJson(yourJson, new TypeToken<HashMap<String, Object>>(){}.getType());
This is more of addendum to Kevin Dolan's answer than a complete answer, but I was having trouble extracting the type from the Number. This is my 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());
}
}
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);
Try this, it will worked. I used it for 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;
}
Replace KioskStatusResource to your class and Integer to your key class.
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 JsonObject
s to Map<String, Object>
and JsonArray
s 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...