Contents

Gson将JSON转换为Map

1. 简介

在本快速教程中,我们将学习如何使用Google的Gson 将 JSON 字符串转换为Map

我们将看到三种不同的方法来实现这一点,并讨论它们的优缺点——并提供一些实际示例。

2. 传递Map.class

通常, Gson 在其Gson类中提供以下 API 来将 JSON 字符串转换为对象

public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException;

从签名中可以清楚地看出,第二个参数是我们打算将 JSON 解析为的对象的类。在我们的例子中,它应该是Map.class

String jsonString = "{'employee.name':'Bob','employee.salary':10000}";
Gson gson = new Gson();
Map map = gson.fromJson(jsonString, Map.class);
Assert.assertEquals(2, map.size());
Assert.assertEquals(Double.class, map.get("employee.salary").getClass());

这种方法将对每个属性的值类型做出最佳猜测。

例如,数字将被强制转换为Double,真假转换为Boolean, 对象转换为LinkedTreeMap

但是,如果有重复的键,强制将失败并抛出JsonSyntaxException

而且,由于类型擦除 ,我们也无法配置这种强制行为。因此,如果我们需要指定键或值类型,那么我们将需要一种不同的方法。

3. 使用TypeToken

为了克服泛型类型擦除的问题,Gson有一个 API 的重载版本

public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException;

我们可以使用 Gson 的TypeToken构造一个带有类型参数的MapTypeToken类返回一个 ParameterizedTypeImpl 实例,即使在运行时也保留键和值的类型:**

String jsonString = "{'Bob' : {'name': 'Bob Willis'},"
  + "'Jenny' : {'name': 'Jenny McCarthy'}, "
  + "'Steve' : {'name': 'Steven Waugh'}}";
Gson gson = new Gson();
Type empMapType = new TypeToken<Map<String, Employee>>() {}.getType();
Map<String, Employee> nameEmployeeMap = gson.fromJson(jsonString, empMapType);
Assert.assertEquals(3, nameEmployeeMap.size());
Assert.assertEquals(Employee.class, nameEmployeeMap.get("Bob").getClass());

现在,如果我们将Map 类型构造为Map<String, Object>,那么解析器仍将像我们在上一节中看到的那样默认。

当然,这仍然要回退到 Gson 来强制原始类型。但是,这些也可以定制。

4. 使用自定义JsonDeserializer

*当我们需要对Map对象的构造进行细粒度控制时,我们可以实现*JsonDeserializer<Map>类型的自定义反序列化器。

看一个例子,假设我们的 JSON 包含员工的姓名作为键,他们的雇佣日期作为它的值。此外,假设日期格式为 yyyy/MM/dd,这不是Gson的标准格式。

我们可以配置 Gson 以不同方式解析我们的Map,然后通过实现 JsonDeserializer

public class StringDateMapDeserializer implements JsonDeserializer<Map<String, Date>> {
    private SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
    @Override
    public Map<String, Date> deserialize(JsonElement elem,
          Type type,
          JsonDeserializationContext jsonDeserializationContext) {
        return elem.getAsJsonObject()
          .entrySet()
          .stream()
          .filter(e -> e.getValue().isJsonPrimitive())
          .filter(e -> e.getValue().getAsJsonPrimitive().isString())
          .collect(
            Collectors.toMap(
              Map.Entry::getKey,
              e -> formatDate(e.getValue())));
    }
    private Date formatDate(Object value) {
        try {
            return format(value.getAsString());
        } catch (ParseException ex) {
            throw new JsonParseException(ex);
        }
    }
}

现在,我们必须在GsonBuilder中针对我们的目标类型Map<String, Date> 注册它并构建一个自定义的Gson对象。

当我们在这个Gson对象上调用fromJson API 时,解析器会调用自定义反序列化器并返回所需的Map实例:

String jsonString = "{'Bob': '2017-06-01', 'Jennie':'2015-01-03'}";
Type type = new TypeToken<Map<String, Date>>(){}.getType();
Gson gson = new GsonBuilder()
  .registerTypeAdapter(type, new StringDateMapDeserializer())
  .create();
Map<String, Date> empJoiningDateMap = gson.fromJson(jsonString, type);
Assert.assertEquals(2, empJoiningDateMap.size());
Assert.assertEquals(Date.class, empJoiningDateMap.get("Bob").getClass());

当我们的Map可能包含异类值并且我们清楚地知道可能存在多少不同类型的值时,这种策略也很有用。

要了解有关 Gson 中的自定义反序列化器的更多信息,请随时阅读 Gson 反序列化简介