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,true 和 false 轉換為 Boolean,對象則轉換為 LinkedTreeMap。
如果存在重複的鍵,則轉換會失敗並拋出 JsonSyntaxException 。
並且,由於 type erasure,我們還無法配置這種轉換行為。因此,如果我們需要指定鍵或值類型,則需要採用不同的方法。
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. 使用自定義 <em >JsonDeserializer</em >>
當我們需要對我們的 `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());
這種策略在我們的地圖可能包含異構值的情況下也很有用,並且我們對可能存在的值類型數量有大致的瞭解。
要了解有關 Gson 中自定義反序列化的更多信息,請查看《Gson 反序列化食譜》。
5. 結論
在本文中,我們學習了多種構建從 JSON 格式字符串中創建地圖的方法。我們還討論了這些變體的正確使用場景。