前言
這個(gè)周末被幾個(gè)技術(shù)博主的同一篇公眾號(hào)文章 fastjson又被發(fā)現(xiàn)漏洞,這次危害可導(dǎo)致服務(wù)癱瘓!
<https://mp.weixin.qq.com/s/yVzZTTR5R6QbspTg5WI5hg> 刷屏,離之前漏洞事件沒(méi)多久,fastjson
又出現(xiàn)嚴(yán)重 Bug。目前項(xiàng)目中不少使用了 fastjson
做對(duì)象與JSON數(shù)據(jù)的轉(zhuǎn)換,又需要更新版本重新部署,可以說(shuō)是費(fèi)時(shí)費(fèi)力。與此同時(shí),也帶給我新的思考,面對(duì)大量功能強(qiáng)大的開(kāi)源庫(kù),我們不能盲目地引入到項(xiàng)目之中,眾多開(kāi)源框架中某個(gè)不穩(wěn)定因素就足以讓一個(gè)項(xiàng)目遭受滅頂之災(zāi)。趁著周末,在家學(xué)習(xí)下同樣具備JSON與對(duì)象轉(zhuǎn)換功能的優(yōu)秀開(kāi)源框架
Gson,并且打算將今后項(xiàng)目使用 fastjson 的地方逐漸換成使用 Gson,記錄下學(xué)習(xí)總結(jié)的內(nèi)容,希望對(duì)小伙伴也有所幫助。
本文所涉及所有代碼片段均在下面?zhèn)}庫(kù)中,感興趣的小伙伴歡迎參考學(xué)習(xí):
https://github.com/wrcj12138aaa/gson-actions
版本支持:
* JDK 8
* Gson 2.8.5
* JUnit 5.5.1
* Lomok 1.18.8
Gson 簡(jiǎn)介
在正式介紹 Gson 之前,我們可以先從官方的wiki
<https://github.com/google/gson/blob/master/UserGuide.md#TOC-Overview>看下 Gson
的描述,了解它是什么?
Gson is a Java library that can be used to convert Java Objects into their
JSON representation. It can also be used to convert a JSON string to an
equivalent Java object。
從描述可以看出,Gson 是用于將 Java 對(duì)象與 JSON格式字符串?dāng)?shù)據(jù)相互轉(zhuǎn)換的 Java 庫(kù)。它起初在Google 內(nèi)部廣泛使用在 Android
平臺(tái) 和 Java 服務(wù)端上。2008 年開(kāi)源之后,成為了谷歌又一個(gè)被廣泛使用的開(kāi)源框架,截止目前(2019.09.08) 在GitHub 上已有1W6
多星,相同作用的類(lèi)庫(kù)還有 Spring Framework 中集成的 Jackson,以及阿里開(kāi)源的 fastjson等。
在特性方面,Gson 提供簡(jiǎn)易的API fromJson/toJson 來(lái)實(shí)現(xiàn) Java 與 JSON 之間的轉(zhuǎn)換,并且能生成緊湊,可讀的 JSON
字符串輸出,還支持復(fù)雜對(duì)象轉(zhuǎn)換和豐富的自定義表示,足以滿足在日常開(kāi)發(fā)中我們絕大部分的 JSON 數(shù)據(jù)處理需求。
我們通常將對(duì)象與JSON字符串間的轉(zhuǎn)換稱之為序列化和反序列化(Serialization/Deserialization)。將 對(duì)象轉(zhuǎn)化成
JSON字符串的過(guò)程稱為序列化,將JSON 字符串轉(zhuǎn)化成對(duì)象的過(guò)程稱為反序列化。
Gson 基本使用
使用 Gson 框架進(jìn)行序列化與反序列操作,都離不開(kāi) com.google.gson.Gson 對(duì)象,它也是 Gson 框架的關(guān)鍵對(duì)象,提供的公共 API
具備了多種序列化和反序列方式。
Gson 對(duì)象的創(chuàng)建主要有兩種方式:
* 使用 new 關(guān)鍵字直接創(chuàng)建:Gson gson = new Gson()
* 由 GsonBuilder 對(duì)象構(gòu)建:Gson gson = new GsonBuilder().create()
通常情況下,上面兩種方式創(chuàng)建的 Gson 對(duì)象在進(jìn)行序列化與反序列操作時(shí)行為都是一樣的,但是第二種方式構(gòu)建 Gson
對(duì)象時(shí),允許進(jìn)行額外的行為定制,比如格式化 JSON 字符串的輸出內(nèi)容,是否序列化null 值等等。
Java 序列化
簡(jiǎn)單對(duì)象的序列化
我們可以通過(guò)下面的例子來(lái)看下通過(guò)上述兩種方式序列化 Java 對(duì)象的不同效果:
public class ResultTest { @Test void test_serialization() { Gson gson = new
Gson(); Result result = new Result(200, "成功", null); String json =
gson.toJson(result); System.out.println("json is " + json); Gson buildedGson =
new GsonBuilder().setPrettyPrinting().serializeNulls().create(); String
buildedJson = buildedGson.toJson(result); System.out.println("buildedJson is "
+ buildedJson); } class Result { private int code; private String message;
private Object data; public Result(int code, String message, Object data) {
this.code = code; this.message = message; this.data = data; } } }
運(yùn)行該測(cè)試用例,在控制臺(tái)可以看到如下日志輸出:
從結(jié)果可以看出,默認(rèn)的 Gson 對(duì)象行為序列化對(duì)象時(shí)會(huì)將 null 值的字段忽略,而
com.google.gson.GsonBuilder#serializeNulls 方法將允許 Gson 對(duì)象序列化 null 字段;并且正常序列化后的
JSON 字符串是緊湊格式,節(jié)省字符串內(nèi)存,使用com.google.gson.GsonBuilder#setPrettyPrinting 方法之后最終輸出的
JSON 字符串是更易讀的格式。當(dāng)然除了這兩個(gè)方法,GsonBuilder 還提供了許多定制序列化和反序列化行為的API,我們將后面的內(nèi)容進(jìn)一步講解。
JosnObject 生成 JSON
除了上述將自定義類(lèi)的對(duì)象轉(zhuǎn)換成 JSON 的方式之外,還可以使用 Gson 框架提供的 JsonObject 構(gòu)建普通對(duì)象,然后使用 toJson 方法生成
JSON 字符串,在原測(cè)試類(lèi)中補(bǔ)充下方測(cè)試類(lèi),并運(yùn)行查看效果如下
@Test void test_jsonObject_serialization() { Gson gson = new Gson();
JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("code", 400);
jsonObject.addProperty("message", "參數(shù)錯(cuò)誤"); String toJson =
gson.toJson(jsonObject); String exceptedJson =
"{\"code\":400,\"message\":\"參數(shù)錯(cuò)誤\"}"; Assertions.assertEquals(exceptedJson,
toJson); //true }
JsonObject 使用 addProperty(property,value) 方法只能用來(lái)添加
String,Number,Boolean,Character這四類(lèi)數(shù)據(jù), 因?yàn)閮?nèi)部是調(diào)用com.google.gson.JsonObject#add, 將
value 封裝成了 JsonPrimitive 對(duì)象,然后保存到了內(nèi)部自定義的LinkedTreeMap 集合變量 members 中;如果需要在
JsonObject 對(duì)象上添加其他對(duì)象時(shí),就需要直接使用add(String property, JsonElement value) 方法添加一個(gè)
JsonElement 對(duì)象。這里的 JsonElement 是一個(gè)抽象類(lèi),JsonObject 和 JsonPrimitive
都繼承了JsonElement,所以我們最終通過(guò)另外的 JsonObject 對(duì)象來(lái)作為原 JsonObject 上的屬性對(duì)象:
Gson gson = new Gson(); JsonObject jsonObject = new JsonObject(); //...
JsonObject nestJsonObject = new JsonObject();
nestJsonObject.addProperty("username", "one");
nestJsonObject.addProperty("score", 99); jsonObject.add("data",
nestJsonObject); String toJson2 = gson.toJson(jsonObject);
System.out.println(toJson2); //
{"code":400,"message":"參數(shù)錯(cuò)誤","data":{"username":"one","score":99}}
JSON 反序列化
簡(jiǎn)單對(duì)象的反序列化
現(xiàn)在我們?cè)賮?lái)看下 JSON 反序列化成 Java 對(duì)象用法,這里主要使用方法是 com.google.gson.Gson#fromJson,它最基礎(chǔ)的用法就是
fromJson(String json, Class<T> classOfT),嘗試將 JSON 字符串轉(zhuǎn)為指定 Class 的對(duì)象,如果轉(zhuǎn)換失敗,就會(huì)拋出
JsonSyntaxException 異常。我們可以在原來(lái)代碼上新增一個(gè)測(cè)試用例,運(yùn)行看下效果:
@Test void test_deserialization() { String json =
"{\"code\":400,\"message\":\"參數(shù)錯(cuò)誤\"}"; Result result = new
Gson().fromJson(json, Result.class); Assertions.assertEquals(400, result.code);
// true Assertions.assertEquals("參數(shù)錯(cuò)誤", result.message); // true }
反序列化 Map
除了將JSON 字符串序列化為自定義的Java 對(duì)象之外,我們還可以轉(zhuǎn)為 Map 集合,Gson 提供了對(duì) Map 集合的轉(zhuǎn)換,使用起來(lái)也十分簡(jiǎn)單:
@Test void test_map() { String jsonString =
"{'employee.name':'one','employee.salary':10}"; Gson gson = new Gson(); Map map
= gson.fromJson(jsonString, Map.class); assertEquals(2, map.size());
assertEquals("one", map.get("employee.name")); assertEquals(Double.class,
map.get("employee.name").getClass()); }
需要注意的是轉(zhuǎn)換后的 Map 對(duì)象真實(shí)類(lèi)型并不是我們經(jīng)常用的 HashMap,而是 Gson 自定義集合LinkedTreeMap ,它實(shí)現(xiàn)Map
接口來(lái)存儲(chǔ)鍵值對(duì),在新增和刪除上實(shí)現(xiàn)上進(jìn)行了優(yōu)化,并且將存儲(chǔ)鍵值對(duì)的順序作為遍歷順序,也就是先存入的先被遍歷到。除此之外,JSON
字符串里的數(shù)值型數(shù)據(jù)都會(huì)轉(zhuǎn)轉(zhuǎn)換為 Double 類(lèi)型,而true/false 數(shù)據(jù)被會(huì)被轉(zhuǎn)換成 Boolean 類(lèi)型,具體判斷依據(jù)可以參考
com.google.gson.internal.bind.ObjectTypeAdapter#read 方法的實(shí)現(xiàn)。
JSON 與 Array,List 轉(zhuǎn)換
JSON 轉(zhuǎn)換 Array
當(dāng)我們正對(duì) JSON 數(shù)據(jù)進(jìn)行數(shù)組轉(zhuǎn)換時(shí),類(lèi)似普通對(duì)象轉(zhuǎn)換的方式即可, toJson 方法直接使用轉(zhuǎn)為 JSON 數(shù)據(jù),fromJson
指定數(shù)組類(lèi)型轉(zhuǎn)換為對(duì)應(yīng)類(lèi)型的數(shù)組。
@Test void test_array() { Gson gson = new Gson(); int[] ints = {1, 2, 3, 4,
5}; String[] strings = {"abc", "def", "ghi"}; String s = gson.toJson(ints);//
[1,2,3,4,5] assertEquals("[1,2,3,4,5]", s); // true String s1 =
gson.toJson(strings);// ["abc", "def", "ghi"]
assertEquals("[\"abc\",\"def\",\"ghi\"]", s1); String[] strings1 =
gson.fromJson(s1, String[].class); assertEquals(strings.length,
strings1.length); // true assertEquals(strings[0], strings1[0]); // true int[]
ints2 = gson.fromJson("[1,2,3,4,5]", int[].class); assertEquals(1, ints2[0]);
// true assertEquals(5, ints2[4]); // true }
JSON 轉(zhuǎn)換 List
要將 List 數(shù)據(jù)轉(zhuǎn)換為 JSON數(shù)據(jù),使用 Gson 的方式與處理 Array 數(shù)據(jù)一樣;這里主要講的是將JSON 數(shù)據(jù)轉(zhuǎn)為 List
對(duì)象的操作略有不同,要將一個(gè) JSON 數(shù)組數(shù)據(jù)轉(zhuǎn)換為一個(gè)自定義類(lèi)的List 時(shí),我們按照原來(lái)的寫(xiě)法如下:
@Test public void givenJsonString_whenIncorrectDeserializing() { Gson gson =
new Gson(); String inputString =
"[{\"id\":1,\"name\":\"one\"},{\"id\":2,\"name\":\"two\"}]"; List<Person>
outputList = gson.fromJson(inputString, List.class); outputList.get(0).getId();
}
但是不幸的是,運(yùn)行這段代碼后會(huì)拋出 ClassCastException 異常,具體描述如下:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be
cast to com.one.learn.Person ...
從上述描述中我們可以知道執(zhí)行 fromJson 之后,反序列化后得到的 List 元素類(lèi)型為 LinkedTreeMap,而不是 Person,所以以
Person 對(duì)象方式訪問(wèn) id 屬性時(shí)就會(huì)拋出ClassCastException 異常。那又該如何處理呢, 我們需要調(diào)用 Gson 的 另外一個(gè)
fromJson 方法:fromJson(String json, Type typeOfT) ,先看下使用方式
@Test public void givenJsonString_whenCorrectDeserializing_() { Gson gson =
new Gson(); String inputString =
"[{\"id\":1,\"name\":\"one\"},{\"id\":2,\"name\":\"two\"}]"; Type type = new
TypeToken<List<Person>>(){}.getType(); List<Person> outputList =
gson.fromJson(inputString, type); int id = outputList.get(0).getId();
assertEquals(1, id); // true assertEquals("one", outputList.get(0).getName());
// true }
這個(gè)方法中的 Type 對(duì)象通過(guò) TypeToken 對(duì)象的 getType 方法獲取到,就是 TypeToken 對(duì)象所關(guān)聯(lián)的泛型類(lèi)型。而這里
TypeToken 是 Gson 為了支持泛型而引入的類(lèi),來(lái)解決 Java 無(wú)法提供泛型類(lèi)型表示的問(wèn)題,由于 TypeToken 的構(gòu)造方法是protected
修飾的,無(wú)法直接構(gòu)造,使用就需要寫(xiě)成new TypeToken<List<String>>() {}.getType() 形式。
Gson 進(jìn)階用法
接觸了 Gson 基本的使用之后,我們接著進(jìn)一步學(xué)習(xí) Gson 的其他用法。
泛型對(duì)象的反序列化
上節(jié)內(nèi)容簡(jiǎn)單接觸了 Gson 對(duì)泛型的支持,接下來(lái)用代碼來(lái)展示下它的強(qiáng)大之處,首先我們將上文的 Result 類(lèi)調(diào)整下接受泛型參數(shù):
class Result<T> { private int code; private String message; private T data;
public Result(int code, String message, T data) { this.code = code;
this.message = message; this.data = data; } }
然后對(duì)一個(gè)有內(nèi)嵌對(duì)象的 JSON字符串進(jìn)行解析成 Result<User> 對(duì)象,示例代碼如下:
@Test void test_genric_object() { String json =
"{\"code\":200,\"message\":\"操作成功\",\"data\":{\"username\": \"one\",\"avater\":
\"image.jpg\"" + "}}"; Type type = new TypeToken<Result<User>>(){}.getType();
Result<User> result = new Gson().fromJson(json, type);
Assertions.assertEquals(200, result.code); Assertions.assertEquals("one",
result.data.getUsername()); Assertions.assertEquals("image.jpg",
result.data.getAvater()); } class User { private String username; private
String avater; public String getUsername() { return username; } public String
getAvater() { return avater; } }
利用 TypeToken 對(duì)象獲取具體泛型類(lèi)型 Result<User> , 然后在 fromJson 方法中傳入就會(huì)根據(jù)對(duì)應(yīng)類(lèi)型的執(zhí)行反序列化操作。
自定義序列化
如果我們要對(duì)Java 對(duì)象的某些字段進(jìn)行特殊處理,比如隱藏某些字段的序列化,對(duì)字段的數(shù)據(jù)格式化處理等,我們可以通過(guò)實(shí)現(xiàn) JsonSerializer
接口,對(duì)序列化邏輯進(jìn)行自定義。例如,我們需要對(duì) Date 類(lèi)型屬性進(jìn)行特定格式的處理,可以聲明 DateSerializer 類(lèi)實(shí)現(xiàn)如下:
class DateSerializer implements JsonSerializer<Date> { SimpleDateFormat
dateTime = new SimpleDateFormat("yyyy-MM-dd"); @Override public JsonElement
serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { return
new JsonPrimitive(dateTime.format(src)); } }
然后在構(gòu)建 Gson 對(duì)象前,利用 GsonBuilder 將 DateSerializer 實(shí)例進(jìn)行注冊(cè),使用方式如下:
Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new
DateSerializer()).create();
這樣一來(lái),一旦遇到要序列化 Date 類(lèi)型的字段時(shí),都會(huì)通過(guò)自定義的 serialize 方法將日期以 yyyy-MM-dd 格式進(jìn)行輸出,如下方的示例代碼:
@Test void test_dateSerializer() { MyObject myObject = new MyObject(new
Date(), "one"); Gson gson = new GsonBuilder().registerTypeAdapter(Date.class,
new DateSerializer()).create(); String json = gson.toJson(myObject); String
exceptedJson = "{\"date\":\"2019-09-08\",\"name\":\"one\"}";
Assertions.assertEquals(exceptedJson, json); // true } class MyObject { private
Date date; private String name; public MyObject(Date date, String name) {
this.date = date; this.name = name; } public MyObject() { } }
自定義反序列化
與自定義序列化實(shí)現(xiàn)方式類(lèi)似,想要自定義反序列化邏輯,就需要同樣要實(shí)現(xiàn)一個(gè)叫 JsonDeserializer
的接口,進(jìn)行自定義反序列化邏輯的實(shí)現(xiàn)。比如現(xiàn)在有個(gè) JSON 字符串內(nèi)容為{"CODE": 400, "MESSAGE": "參數(shù)錯(cuò)誤"}
,需要被反序列化為前文提到的 Result 對(duì)象,由于字段名不一樣,為了實(shí)現(xiàn)對(duì)應(yīng)的轉(zhuǎn)換,就需要自定義 ResultDeserializer 類(lèi),具體實(shí)現(xiàn)如下:
class ResultDeserializer implements JsonDeserializer<Result> { @Override
public Result deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException { JsonObject
object = json.getAsJsonObject(); Result<Object> result = new
Result<>(object.getAsJsonPrimitive("CODE").getAsInt(),object.getAsJsonPrimitive("MESSAGE").getAsString(),
null); return result; } }
接下來(lái)就是利用 GsonBuilder 注冊(cè) ResultDeserializer 實(shí)例,生成對(duì)應(yīng)的 Gson 對(duì)象,并進(jìn)行反序列化操作:
@Test void test_resultDeserializer() { //language=JSON String json =
"{\"CODE\": 400,\"MESSAGE\": \"參數(shù)錯(cuò)誤\"}"; Gson gson = new
GsonBuilder().registerTypeAdapter(Result.class, new ResultDeserializer())
.create(); Result result = gson.fromJson(json, Result.class);
Assertions.assertEquals(400, result.code); // true
Assertions.assertEquals("參數(shù)錯(cuò)誤", result.message); // true }
Gson 常用注解
Gson 除了提供一些 API 供開(kāi)發(fā)者使用之外,還有一些具有特性的注解可以使用,接下來(lái)就介紹在 Gson 中最常用的注解。
@Expose
這個(gè)注解只能用在字段上,作用就是注明對(duì)應(yīng)的字段是否將在序列化或者反序列化時(shí)暴露出來(lái),有兩個(gè)屬性 serialize 和 deserialize ,默認(rèn)都為
true。當(dāng)給一個(gè)字段加上 注解@Expose(serialize = true, deserialize = false)
,則表示了該字段盡在序列化時(shí)可見(jiàn),在反序列化時(shí)會(huì)忽略賦值。需要額外注意的一點(diǎn)是,@Expose 注解只有在用 GsonBuilder 方式構(gòu)建 Gson
時(shí)有限,并且構(gòu)建前必須調(diào)用excludeFieldsWithoutExposeAnnotation 方法,下面是具體的使用示例:
@Test void test_expose() { MySubClass subclass = new MySubClass(42L, "the
answer", "Verbose field not to serialize"); MyClass source = new MyClass(1L,
"foo", "bar", subclass); Gson gson = new
GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); String s =
gson.toJson(source); System.out.println(s); //
{"id":1,"name":"foo","subclass":{"id":42,"description":"the
answer","otherVerboseInfo":"Verbose field not to serialize"}} } @Data
@AllArgsConstructor class MyClass { private long id; @Expose(serialize = false,
deserialize = true) private String name; private transient String other;
@Expose private MySubClass subclass; } @Data @AllArgsConstructor class
MySubClass { @Expose private long id; @Expose private String description;
@Expose private String otherVerboseInfo; }
在 Gson 中 transient 關(guān)鍵字修飾的字段默認(rèn)不會(huì)被序列化和反序列化,這個(gè)行為是與 Java 原生的序列化和反序列化操作一致的。
@Since
該注解用于標(biāo)記對(duì)應(yīng)字段或者類(lèi)型的版本,讓 Gson 可以指定版本號(hào)進(jìn)行序列化和反序列化操作。當(dāng)Web服務(wù)上的 JSON
數(shù)據(jù)對(duì)應(yīng)的類(lèi)存在多個(gè)版本的字段時(shí),這個(gè)注解就十分有用。
同樣地,該注解只針對(duì)使用 GsonBuilder 方式構(gòu)建的 Gson 對(duì)象,并且使用 setVersion
方法指明版本號(hào)時(shí)有效,只解析對(duì)象中對(duì)應(yīng)版本的字段,下面為具體示例:
public class VersioningSupportTest { @Test void test() { VersionedClass
versionedObject = new VersionedClass(); Gson gson = new
GsonBuilder().setVersion(1.0).create(); String jsonOutput =
gson.toJson(versionedObject); System.out.println(jsonOutput); //
{"newField":"new","field":"old"} } } class VersionedClass { @Since(1.1) private
final String newerField; @Since(1.0) private final String newField; private
final String field; public VersionedClass() { this.newerField = "newer";
this.newField = "new"; this.field = "old"; } }
@SerializedName
這個(gè)注解使用起來(lái)比較簡(jiǎn)單,也很有用。@SerializedName 指定了成員字段被序列化和反序列化時(shí)所采用的名稱下面是具體使用方式:
public class JSONFieldNamingSupportTest { private class SomeObject {
@SerializedName("custom_naming") private final String someField; private final
String someOtherField; public SomeObject(String a, String b) { this.someField =
a; this.someOtherField = b; } } @Test void test() { SomeObject someObject = new
SomeObject("first", "second"); String jsonRepresentation =
gson.toJson(someObject); System.out.println(jsonRepresentation); //
{"custom_naming":"first","someOtherField":"second"} SomeObject someObject1 =
gson.fromJson(jsonRepresentation, SomeObject.class);
System.out.println(someObject1); // SomeObject{someField='first',
someOtherField='second'} } }
@JsonAdapter
不同于上面的注解,@JsonAdapter 只作用于類(lèi)上,主要作用就是代替 GsonBuilder.registerTypeAdapter
方法的執(zhí)行,直接通過(guò)@JsonAdapter(aClass.class) 方式指定 JsonDeserializer 對(duì)象或者 JsonSerializer
對(duì)象,可以起到相同的想過(guò),并且優(yōu)先級(jí)比GsonBuilder.registerTypeAdapter的優(yōu)先級(jí)更高,由于只是將
registerTypeAdapter方法執(zhí)行簡(jiǎn)化成了注解方法,這里就不再演示,直接在前文自定義反序列化一節(jié)的 Result 類(lèi)上使用就可以看到效果。
結(jié)語(yǔ)
本文主要學(xué)習(xí)總結(jié)了 Gson 框架的序列化和反序列操作使用方式,以及介紹了 Gson 多種特性用法,希望對(duì)處理 JSON 數(shù)據(jù)感到頭疼的小伙伴有所幫助。
推薦閱讀
* 一文掌握 Spring Boot Profiles
<https://mp.weixin.qq.com/s/TfA8rSwYNpW3YV_N8oLJnw>
* 如何優(yōu)雅關(guān)閉 Spring Boot 應(yīng)用 <https://mp.weixin.qq.com/s/-t2hrrVMBpPmVEzDcC8J5w>
* 需要接口管理的你了解一下? <https://mp.weixin.qq.com/s/uWRnRhH4et-XSD101Xh6LQ>
* Java 之 Lombok 必知必會(huì) <https://mp.weixin.qq.com/s/2qkNz4VPgnixXjaVYUkvvQ>
* Java 微服務(wù)新生代之 Nacos <https://mp.weixin.qq.com/s/vS36glyNoD26GL6cbNs5Qw>
參考資料
* https://github.com/google/gson/blob/master/UserGuide.md
* https://www.jianshu.com/p/e740196225a4
* https://juejin.im/post/5aad29f8518825558453c6c9
* https://www.baeldung.com/gson-deserialization-guide
* https://www.baeldung.com/gson-string-to-jsonobject
熱門(mén)工具 換一換
