1. Introduction
1.绪论
In this quick tutorial, we’ll learn how to convert a JSON string to a Map using Gson from Google.
在这个快速教程中,我们将学习如何使用Google的Gson将JSON字符串转换成Map。。
We’ll see three different approaches to accomplish that and discuss their pros and cons – with some practical examples.
我们将看到三种不同的方法来实现这一目标,并讨论它们的优点和缺点–结合一些实际的例子。
2. Passing Map.class
2.传递Map.class
In general, Gson provides the following API in its Gson class to convert a JSON string to an object:
一般来说,Gson在其Gson类中提供了以下API,以将JSON字符串转换为对象。
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException;
From the signature, it’s very clear that the second parameter is the class of the object which we intend the JSON to parse into. In our case, it should be Map.class:
从签名中可以看出,第二个参数是我们想把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());
This approach will make its best guess regarding the value type for each property.
这种方法将对每个属性的价值类型作出最佳猜测。
For example, numbers will be coerced into Doubles, true and false into Boolean, and objects into LinkedTreeMaps.
例如,数字将被强制转化为Doubles,true和false转化为Boolean,而对象则转化为LinkedTreeMaps。
If there are duplicate keys, though, coercion will fail and it will throw a JsonSyntaxException.
如果有重复的键,强制执行会失败,并且会抛出一个JsonSyntaxException。。
And, due to type erasure, we won’t be able to configure this coercion behavior either. So, if we need to specify the key or value types, then we’ll need a different approach.
而且,由于type erasure,我们也将无法配置这种强制行为。所以,如果我们需要指定键或值的类型,那么我们就需要一个不同的方法。
3. Using TypeToken
3.使用TypeToken
To overcome the problem of type-erasure for the generic types, Gson has an overloaded version of the API:
为了克服通用类型的类型清除问题,Gson有一个API的重载版本。
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException;
We can construct a Map with its type parameters using Gson’s TypeToken. The TypeToken class returns an instance of ParameterizedTypeImpl that preserves the type of the key and value even at runtime:
我们可以使用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());
Now, if we construct our Map type as Map<String, Object>, then the parser will still default as we saw in the previous section.
现在,如果我们将我们的Map类型构造为Map<String, Object>,那么解析器仍然会像我们在上一节看到的那样默认。
Of course, this still falls back to Gson for coercing primitive types. Those, however, can be customized, too.
当然,这还是要归功于Gson来强制执行原始类型。然而,这些也是可以定制的。
4. Using Custom JsonDeserializer
4.使用自定义JsonDeserializer
When we need fine-grained control over the construction of our Map object, we can implement a custom deserializer of type JsonDeserializer<Map>.
当我们需要对我们的Map对象的构建进行精细控制时,我们可以实现一个JsonDeserializer<Map>类型的自定义反序列化器。。
To see an example, let’s assume our JSON contains the employee’s name as key and their hire date as its value. Further, let’s assume the date’s format is yyyy/MM/dd, which is not a standard format for Gson.
为了看一个例子,让我们假设我们的JSON包含雇员的名字作为键,他们的雇用日期作为它的值。此外,我们假设日期的格式是yyyy/MM/dd,这不是Gson的标准格式。
We can configure Gson to parse our map differently, then, by implementing a JsonDeserializer:
那么,我们可以通过实现一个JsonDeserializer:来配置Gson来解析我们的地图。
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);
}
}
}
Now, we have to register it in the GsonBuilder against our target type Map<String, Date> and build a customized Gson object.
现在,我们必须在GsonBuilder中针对我们的目标类型Map<String, Date注册它,并建立一个自定义的Gson对象。
When we call the fromJson API on this Gson object, the parser invokes the custom deserializer and returns the desired Map instance:
当我们在这个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());
This tactic is also useful when our map may contain heterogeneous values and we have a fair idea of how many different types of values could be there.
当我们的地图可能包含异质值,并且我们对那里可能有多少不同类型的值有一个合理的想法时,这种策略也很有用。
To learn more about a custom deserializer in Gson, feel free to go through the Gson Deserialization Cookbook.
要了解有关Gson中自定义反序列化器的更多信息,请随时浏览Gson反序列化手册。
5. Conclusion
5.结论
In this short article, we learned several ways to construct a map from a JSON-formatted string. And we also discussed proper use-cases for these variations.
在这篇短文中,我们学习了几种从JSON格式的字符串构建地图的方法。我们还讨论了这些变化的正确用例。
The source code for the examples is available over on GitHub.
这些例子的源代码可以在GitHub上找到over。