如何使用Gson将JSON转换为HashMap?

319

我正在向服务器请求数据,服务器以JSON格式返回数据。在请求时将HashMap转换为JSON并不难,但反过来似乎有点棘手。JSON响应如下:

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

我应该用什么方法最容易地访问这些数据?我正在使用 GSON 模块。


31
Map<String,Object> result = new Gson().fromJson(json, Map.class); 可以在 gson 2.6.2 版本中使用。意思是将传入的 json 字符串转化成一个键值对集合(key为String类型,value为Object类型)并存储在result变量中。 - Ferran Maylinch
16个回答

529

这是您需要的:

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
不错,但我不喜欢使用TypeToken——它会进行隐式转换。 - AlikElzin-kilaka
1
这个例子中的 JSON 是否有效? - Evan Kairuz
2
@EvanKairuz 不是这样的。应该是 {"k1":"苹果","k2":"橙子"} - Vadim Kotov
3
new Gson().fromJson(jsonData, new TypeToken<Map<String, Integer>>(){}.getType()); 被转换成 Double 而不是 Integer 吗? - roottraveller
1
这个解决方案在嵌套对象方面给我带来了问题,所以最终我使用了 new Gson().fromJson(json, Map.class); - Ferran Maylinch
显示剩余3条评论

124

这段代码有效:

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

3
在将它们转换为字符串之前,这将把整数转换为浮点数,但它可以用于将JSON转换为映射以进行比较。 - louielouie
13
对我来说很有效,但我将地图更改为“Map<String, Object>”,因为如果JSON不仅仅是字符串,则会出现错误。 - Moshe Shaham
6
这会给人留下错误的印象。对于参数化类型,正确的解决方案是使用 TypeToken - Sotirios Delimanolis
这将是一个通用的解决方案,适用于所有类型,但有点不常见。 - Leon

80
我知道这是一个相当久远的问题,但我正在寻找一种通用的方法来将嵌套的 JSON 反序列化为 Map<String, Object>,却没有找到任何东西。
我的 yaml 反序列化器默认将未指定类型的 JSON 对象转换为 Map<String, Object>,但 gson 似乎不会这样做。幸运的是,您可以使用自定义反序列化程序完成此操作。
我使用以下反序列化程序自然地反序列化任何内容,将 JsonObject 默认转换为 Map<String, Object>,将 JsonArray 转换为 Object[],其中所有子级也被类似地反序列化。
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;
  }
}

handlePrimitive方法中的混乱是为了确保您只获得Double、Integer或Long,如果您愿意获得BigDecimals,则可能会更好或者至少可以简化,我相信这是默认值。

您可以像下面这样注册此适配器:

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

然后像这样调用:

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

我不确定为什么在gson中这不是默认行为,因为在大多数其他的半结构化序列化库中都是...


1
虽然我不太确定如何处理返回的对象。尽管我知道它们是字符串,但似乎无法将它们转换为字符串。 - Matt Zukowski
1
啊哈!诀窍在于递归调用反序列化器,而不是调用context.deserialize()。 - Matt Zukowski
1
你有代码吗,Matt?我正在尝试对反序列化器进行更改,但我真的看不出你的观点。 - Romain Piel
5
Gson现在默认似乎具有Kevin Dolan在他的代码片段中所需的行为。 - eleotlecram
1
@SomeoneSomewhere 请看这里的被接受的答案:https://dev59.com/lmUp5IYBdhLWcg3w665C - M.Y.
显示剩余5条评论

41

使用谷歌的Gson 2.7版本(可能也适用于早期版本,但我已经测试了当前版本2.7),操作就像这样简单:

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

该方法返回一个类型为com.google.gson.internal.LinkedTreeMapMap对象,并可以递归地处理嵌套对象、数组等。

我按照以下方式运行了 OP 的示例(简单地使用单引号替换双引号并删除空格):

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

并得到了以下输出:

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}

34

针对新的Gson库更新:
现在您可以直接将嵌套的Json解析为Map,但是请注意,如果您尝试将Json解析为Map<String, Object>类型,它会引发异常。要解决此问题,只需声明结果为LinkedTreeMap类型。以下是示例:

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

我应该从哪里导入LinkedTreeMap?我在Gson代码中找不到它。 - HelpMeStackOverflowMyOnlyHope
据我所记,LinkedTreeMap 是在新的 Gson 库中定义的。 您可以在这里查看: https://code.google.com/p/google-gson/source/browse/trunk/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java?r=1204 - Hoang Nguyen Huu
1
对我来说,使用Map<String,Object> result = gson.fromJson(json , Map.class);同样有效。使用的是gson 2.6.2版本。 - Ferran Maylinch
3
根据上面的建议,对我有用的是将嵌套的HashMap<String, Object>转换为LinkedTreeMap对象,并在循环中遍历LinkedTreeMap键并填充新的HashMap,因为TypeToken技巧在嵌套时对我无效。不知道为什么不能直接转换,但满足了我的要求。请注意,这里不提供解释性内容。 - gcr

14

我有完全相同的问题,最终来到了这里。 我有一种不同的方法,似乎更加简单(可能是较新版本的gson?)。

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

以下是JSON数据:
{
  "map-00": {
    "array-00": [
      "entry-00",
      "entry-01"
     ],
     "value": "entry-02"
   }
}

以下内容:
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);

输出

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

您可以在遍历jsonObject时使用instanceof进行动态检查。类似这样:
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");
} ...

这句话的意思是“它对我有效,所以一定对你也有效 ;-)”。

9
自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
尝试这个,它会起作用。我在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;
}

KioskStatusResource替换为您的类名,Integer替换为您的键类名。


在 HashMap 抛出 LinkedTreeMap 异常后,这个方法对我有效。 - mmm111mmm

2

以下是我一直在使用的内容:

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
这里有一个一行代码可以完成它的方法:
HashMap<String, Object> myMap =
   gson.fromJson(yourJson, new TypeToken<HashMap<String, Object>>(){}.getType());

是的,这只是一行代码,但请记住 new TypeToken<HashMap<String, Object>>(){} 将创建一个新的内联子类,所有的 linter 至少都会发出警告,我想。 - Martin Meeser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接