I have json object with arbitary values inside. And I want to deserialize it in a Map. Everything is ok except converting integers to a doubles. See example:
JSON only has a single Number type and there is no way for the parser to automatically tell what type to convert it to.
If you aren't going to use a strongly typed object graph, consider using the JsonElement types:
JsonObject root = new Gson().fromJson(json, JsonObject.class);
int num = root.getAsJsonObject("inner_obj").get("num").getAsInt();
Been searching for a solution to the nested Map problem myself and "이종은" above was the first answer that actually helped in the non trivial use cases.
Since the solution above only handled Number I updated the solution to provide generic parsing capability for String and booleans also, see the updated code below:
private static class MapDeserializer implements JsonDeserializer<Map<String, Object>> {
public Map<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Map<String, Object> m = new LinkedHashMap<String, Object>();
JsonObject jo = json.getAsJsonObject();
for (Entry<String, JsonElement> mx : jo.entrySet()) {
String key = mx.getKey();
JsonElement v = mx.getValue();
if (v.isJsonArray()) {
m.put(key, g.fromJson(v, List.class));
} else if (v.isJsonPrimitive()) {
Number num = null;
ParsePosition position=new ParsePosition(0);
String vString=v.getAsString();
try {
num = NumberFormat.getInstance(Locale.ROOT).parse(vString,position);
} catch (Exception e) {
}
//Check if the position corresponds to the length of the string
if(position.getErrorIndex() < 0 && vString.length() == position.getIndex()) {
if (num != null) {
m.put(key, num);
continue;
}
}
JsonPrimitive prim = v.getAsJsonPrimitive();
if (prim.isBoolean()) {
m.put(key, prim.getAsBoolean());
} else if (prim.isString()) {
m.put(key, prim.getAsString());
} else {
m.put(key, null);
}
} else if (v.isJsonObject()) {
m.put(key, g.fromJson(v, Map.class));
}
}
return m;
}
}
private static class ListDeserializer implements JsonDeserializer<List<Object>> {
public List<Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<Object> m = new ArrayList<Object>();
JsonArray arr = json.getAsJsonArray();
for (JsonElement jsonElement : arr) {
if (jsonElement.isJsonObject()) {
m.add(g.fromJson(jsonElement, Map.class));
} else if (jsonElement.isJsonArray()) {
m.add(g.fromJson(jsonElement, List.class));
} else if (jsonElement.isJsonPrimitive()) {
Number num = null;
try {
num = NumberFormat.getInstance().parse(jsonElement.getAsString());
} catch (Exception e) {
}
if (num != null) {
m.add(num);
continue;
}
JsonPrimitive prim = jsonElement.getAsJsonPrimitive();
if (prim.isBoolean()) {
m.add(prim.getAsBoolean());
} else if (prim.isString()) {
m.add(prim.getAsString());
} else {
m.add(null);
}
}
}
return m;
}
}
private static Gson g = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserializer()).registerTypeAdapter(List.class, new ListDeserializer()).setDateFormat("yyyy-MM-dd HH:mm:ss").create();
To avoid possible ClassCastException, it is better to cast to Number
first. In the following code map
was deserialized from JSON as a Map
with no generics.
int numberOfPages = ((Number) map.get("number_of_pages")).intValue();
Here is my example, the first part is the definition of the class that has an int type field.
import com.google.api.client.util.Key;
public class Folder {
public static final String FIELD_NAME_CHILD_COUNT = "childCount";
@Key(FIELD_NAME_CHILD_COUNT)
public final int childCount;
public Folder(int aChildCount) {
childCount = aChildCount;
}
}
Then the TypeAdapter to convert the number type in Gson to a Java object.
GsonBuilder gsb = new GsonBuilder();
gsb.registerTypeAdapter(Folder.class, new JsonDeserializer<Folder>() {
@Override
public Folder deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
int value = json.getAsJsonObject().get("childCount").getAsJsonPrimitive().getAsInt();
return new Folder(value);
}
}
);
The third part is the test data, and it works.
String gsonData = new String("\"folder\":{\"childCount\":0}");
Register type adapter for Map
:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Map.class, new JsonDeserializer<Map>() {
@Override
public Map deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
LinkedTreeMap<String,Object> m = new LinkedTreeMap<String, Object>();
JsonObject jo = json.getAsJsonObject();
for (Map.Entry<String, JsonElement> mx : jo.entrySet()){
String key = mx.getKey();
JsonElement v = mx.getValue();
if(v.isJsonArray()){
m.put(key, context.deserialize(v, List.class));
}else if(v.isJsonPrimitive()){
Object value = v.getAsString();
try {
Object numValue = NumberFormat.getInstance().parse((String)value);
if (numValue != null && numValue.toString().equals(value)) {
value = numValue;
}
} catch (Exception e) {
}
m.put(key, value);
}else if(v.isJsonObject()){
m.put(key,context.deserialize(v, Map.class));
}
}
return m;
}
})
.create();
and deserialize using it like: gson.fromJson(instanceJson, Map.class)
, where instanceJson
is an json
of object which should be deserialized into Map
.
It's my code
private static class MapDeserializer implements JsonDeserializer<Map<String,Object>> {
public Map<String,Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Map<String,Object> m = new LinkedHashMap<String, Object>();
JsonObject jo = json.getAsJsonObject();
for (Entry<String, JsonElement> mx : jo.entrySet()){
String key = mx.getKey();
JsonElement v = mx.getValue();
if(v.isJsonArray()){
m.put(key, g.fromJson(v, List.class));
}else if(v.isJsonPrimitive()){
Number num = null;
try {
num = NumberFormat.getInstance().parse(v.getAsString());
} catch (Exception e) {
e.printStackTrace();
}
m.put(key,num);
}else if(v.isJsonObject()){
m.put(key,g.fromJson(v, Map.class));
}
}
return m;
}
}
private static class ListDeserializer implements JsonDeserializer<List<Object>> {
public List<Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<Object> m = new ArrayList<Object>();
JsonArray arr = json.getAsJsonArray();
for (JsonElement jsonElement : arr) {
if(jsonElement.isJsonObject()){
m.add(g.fromJson(jsonElement, Map.class));
}else if(jsonElement.isJsonArray()){
m.add(g.fromJson(jsonElement, List.class));
}else if(jsonElement.isJsonPrimitive()){
Number num = null;
try {
num = NumberFormat.getInstance().parse(jsonElement.getAsString());
} catch (Exception e) {
}
m.add(num);
}
}
return m;
}
}
private static Gson g = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserializer()).registerTypeAdapter(List.class, new ListDeserializer()).setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();