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构造一个带有类型参数的Map。TypeToken类返回一个 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 反序列化简介 。