引言
- 使用
Gson
已經(jīng)有好長時間了又活,但是一直停留在使用的層面上,因此在對它的好奇心尚未消失之前锰悼,我得跟它做個了斷 柳骄。 - 關(guān)于
Gson
,將會有幾篇長篇解讀文箕般,用于記錄自己淺顯的見解耐薯。
使用默認配置的Gson
- 對于
Json
數(shù)據(jù)的解析,我們總是希望能夠只是通過input -> output
過程就能得到想要的結(jié)果丝里,而不用手動去逐個解析相關(guān)字段曲初,因為太費事了。出于這種需求杯聚,Gson
也在盡力配合臼婆,比如以下示例:
//System.out.println("");
final String jsonStr = "{'data':'this is data', 'result':{'name':'horseLai', 'age':24}}";
Gson gson = new Gson();
//輸出:{"data":"hello gson","result":{"name":"horseLai","age":20}}
Data data = new Data();
User user = new User();
data.setResult(user);
data.setData("hello gson");
user.setAge(20);
user.setName("horseLai");
String json = gson.toJson(data);
System.out.println(json);
//輸出:Data{data='this is data', result=User{name='horseLai', age=24}}
Data data1 = gson.fromJson(jsonStr, Data.class);
System.out.println(data1 );
static class Data {
private String data;
private User result;
// 省略set/get/toString
}
static class User {
private String name;
private int age;
// 省略set/get/toString
}
-
那么
fromJson
和toJson
發(fā)生了什么?-
我們可以思考一下械媒,如果我們需要從
json
字符串中分離出字段名及其對應(yīng)的值目锭,然后將這些值填充賦值到Java
實體類中對應(yīng)的字段中评汰,我們要做哪些工作纷捞,如何進行?-
我的思路:
- 分解
json
字符串痢虹,直覺是使用正則,但是仔細思考一番會發(fā)現(xiàn)主儡,對于簡單的json
數(shù)據(jù)來說奖唯,Ok,但是對象層級深一點的話就復(fù)雜了糜值,有沒有更好的方式呢丰捷?使用棧和Map
配合的方式,遇到{
或[
寂汇,標記為對象或數(shù)組的開始病往,然后往棧中壓入數(shù)據(jù),遇到:
則表示讀取到了一個字段的名稱骄瓣,接著去除''
和一些空白符號后將字段名作為Map
的key
停巷,清空棧,接著壓棧榕栏,此時要解析值畔勤,那么可以將'
',
'}]
'}
等作為值的起點或終點,標記扒磁,取值庆揪,最終將字段名和值存到Map
中,那么這種方法對于復(fù)雜多層級多類型的json
數(shù)據(jù)而言也是OK的妨托,只要標記好是對象還是數(shù)組還是普通數(shù)據(jù)類型缸榛,然后在Map
中做好對應(yīng)于Json
數(shù)據(jù)的層級即可。如果數(shù)據(jù)是HTML
的話兰伤,會比較不好判斷内颗,因為其中包含的符號可能會比較多,干擾判斷医清。 - 分離了
json
數(shù)據(jù)中的字段名稱和值起暮,接著填充賦值實體類就容易了,反射通過data.getClass().getField("name").set(xxx)
的方式一條龍服務(wù)会烙。
- 分解
-
我的思路:
思考過后负懦,帶著疑問與好奇,通過追蹤柏腻,在
Gson#fromJson
方法中有如下代碼纸厉,可以看出,當(dāng)使用Gson#fromJson
將Json
數(shù)據(jù)轉(zhuǎn)換到Java
實體類時五嫂,會最終調(diào)用TypeAdapter#read
方法進行數(shù)據(jù)填充操作颗品。
public <T> T fromJson(String json, Class<T> classOfT) throws JsonIOException, JsonSyntaxException { // 省略無關(guān)代碼肯尺、調(diào)用層次若干.. TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT); TypeAdapter<T> typeAdapter = getAdapter(typeToken); T object = typeAdapter.read(reader); // . . . return Primitives.wrap(classOfT).cast(object); }
- 而在
Gson#fromJson
方法中有如下代碼,可以看出躯枢,當(dāng)我們使用Gson#fromJson
將Json
數(shù)據(jù)轉(zhuǎn)換到我們的Java
實體類時则吟,會最終調(diào)用TypeAdapter#write
方法進行數(shù)據(jù)填充操作。
public void toJson(Object src) throws JsonIOException { // 省略無關(guān)代碼锄蹂、調(diào)用層次若干.. // 最終會調(diào)用 Streams.write(jsonElement, writer); } public static void write(JsonElement element, JsonWriter writer) throws IOException { // 實際 TypeAdapters.JSON_ELEMENT.write(writer, element); } // 注意到它的泛型是JsonElement public static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() { // 省略無關(guān)代碼若干..}
-
-
那么
TypeAdapter<T>
有什么用氓仲,怎么用?-
TypeAdapter<T>
,類型適配器得糜,包含write``read
兩個核心抽象方法敬扛,用于往數(shù)據(jù)流中寫/讀數(shù)據(jù)。
public abstract class TypeAdapter<T> { // ... public abstract void write(JsonWriter out, T value) throws IOException; public abstract T read(JsonReader in) throws IOException; }
- 在
TypeAdapters
中包含有它的若干個基本實現(xiàn)類朝抖,比如在Gson#toJson
中使用到的TypeAdapters.JSON_ELEMENT
啥箭,代碼如下(其中省略了很多操作相同的代碼,感興趣的朋友可以自行查看源碼)治宣,邏輯可看注釋急侥;
public static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() { @Override public JsonElement read(JsonReader in) throws IOException { switch (in.peek()) { // 從JsonReader的棧中拿到對應(yīng)的字段類型,接著去匹配炼七、讀取 case STRING: return new JsonPrimitive(in.nextString()); //. . . case BEGIN_ARRAY: // 數(shù)組和對象是比較特殊的缆巧,因為他們有自己的字段、對象或數(shù)組豌拙, // 并代表著層級的深度陕悬,因此讀寫時都需要注意標識好 JsonArray array = new JsonArray(); in.beginArray(); // 標識數(shù)組起點,告訴它這是個數(shù)組 while (in.hasNext()) { // 接著通過遞歸讀取按傅、匹配字段類型捉超,并放到數(shù)組中 array.add(read(in)); } in.endArray(); // 告訴gson,這個數(shù)組已經(jīng)讀完了 return array; // . . . } // 上面分析了從流中讀取并構(gòu)建Json對象的過程唯绍,類似于拆箱操作拼岳,那么寫過程就是裝箱操作了 @Override public void write(JsonWriter out, JsonElement value) throws IOException { if (value == null || value.isJsonNull()) { out.nullValue(); } else if (value.isJsonPrimitive()) { // 寫基本類型,注意String也會歸類到基本類型 JsonPrimitive primitive = value.getAsJsonPrimitive(); if (primitive.isNumber()) { out.value(primitive.getAsNumber()); } //. . . } else if (value.isJsonArray()) { // 寫數(shù)組况芒,跟讀一個道理啦 out.beginArray(); // 標識起點 for (JsonElement e : value.getAsJsonArray()) { write(out, e); } out.endArray(); // 標識終點 }// . . . } };
-
定制使用
TypeAdapter<T>
- 上面分析了
TypeAdapter<T>
的作用惜纸,并通過TypeAdapters.JSON_ELEMENT
了解了它的用法,很多時候绝骚,我們可能需要定制我們自己的TypeAdapter<T>
耐版,以便適應(yīng)需求,那么接著走起压汪。
final String jsonStr = "{'data':'this is data', 'result':{'name':'horseLai', 'age':24}}";
//輸出:Data{data='this is data', result=User{name='horseLai', age=24}}
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class, new MyTypeAdapter())
.enableComplexMapKeySerialization()
.serializeNulls()
.setDateFormat(DateFormat.LONG)
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.setPrettyPrinting()
.setVersion(1.0)
.create();
TypeAdapter<Data> adapter = gson.getAdapter(Data.class);
try {
StringReader stringReader = new StringReader(jsonStr);
Data read = adapter.read(new JsonReader(stringReader));
System.out.println(read);
} catch (IOException e) {
e.printStackTrace();
}
-
注意粪牲,自定義
TypeAdapter
時,對于要解析的這個json
數(shù)據(jù)止剖,gson
是按照字段名->字段值逐條讀的(字段名稱(nextName
)也會讀到腺阳,并且如果不先讀名稱而直接讀值(next<類型>
)的話落君,會出錯),寫操作的話寫值就好了亭引,讀/寫有個共同的特點绎速,那就是一定要正確標識對象和數(shù)組的起始位置和終止位置,不然肯定會出錯痛侍,標識起始/終止位置的重要方法如下朝氓,JsonWriter
和JsonReader
都有:-
beginObject()
用于標識馬上要寫入/讀取的是一個Json
對象魔市; -
endObject()
用于標識這個Json
對象已經(jīng)寫/讀完了主届; -
beginArray()
用于標識馬上要寫入/讀取的是一個Json
數(shù)組; -
endArray()
用于標識這個Json
數(shù)組已經(jīng)寫/讀完了待德;
-
// 對應(yīng)于我們的`Json`數(shù)據(jù)定制
static class MyTypeAdapter extends TypeAdapter<Data> {
@Override
public void write(JsonWriter out, Data value) throws IOException
{
if (value == null) {
out.nullValue();
return;
}
out.setLenient(true);
out.beginObject();
out.name("data");
out.value(value.data);
out.name("result");
out.beginObject();
out.name("name");
out.value(value.getResult().name);
out.name("age");
out.value(value.getResult().age);
out.endObject();
out.endObject();
}
@Override
public Data read(JsonReader in) throws IOException
{
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
Data data = new Data();
User user = new User();
in.setLenient(true);
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) { // 先讀取字段名稱君丁,然后逐個去匹配
case "data":
data.setData(in.nextString());
break;
case "result":
in.beginObject();
break;
case "name":
user.setName(in.nextString());
break;
case "age":
user.setAge(in.nextInt());
break;
}
}
data.setResult(user);
in.endObject();
in.endObject();
return data;
}
}
- 上面代碼中有個叫
setLenient
的方法,設(shè)置這個方法有啥作用呢将宪?可以追溯到下面源碼绘闷,其中lenient
就是setLenient
設(shè)置的值,可以看出它用于決定是否對value
進行合法性檢查较坛,表示是否容忍印蔗,false
表示0
容忍,必須檢查丑勤,true
表示容忍华嘹,你隨意。
public JsonWriter value(Number value) throws IOException {
// . . .
String string = value.toString();
if (!lenient && (string.equals("-Infinity")
|| string.equals("Infinity") || string.equals("NaN"))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue();
out.append(string);
return this;
}
至此法竞,可能我們會有所疑問耙厚,即便我們沒有注冊我們定制的
TypeAdapter
也可以通過toJson
,fromJson
解析數(shù)據(jù)岔霸,那它是通過誰解析的呢薛躬?我們繼續(xù)追蹤。其中
getAdapter
的偽代碼如下(源碼請自行查看)呆细,首先會從TokenCache
和ThreadLocal
中查找型宝,看有沒有使用過的緩存,如果有絮爷,那么返回對應(yīng)的值趴酣,如果都沒有,那么會遍歷Gson
中的工廠集合略水,逐一使用工廠去創(chuàng)建對應(yīng)于type
的TypeAdapter
价卤,顯然不匹配的話會返回null
, 如果最終實在沒有找到合適的,那么只能拋異常了渊涝。
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
if (TypeToken緩存中是否存在){
return (TypeAdapter<T>) cached;
}
if (ThreadLocal中是否保存有){
return (FutureTypeAdapter<T>) cached;
}
for (TypeAdapterFactory factory : factories) {
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate); // 緩存至ThreadLocal
typeTokenCache.put(type, candidate); // 緩存至TokenCache
return candidate;
}
}
// 實在沒找到可以匹配的
throw new IllegalArgumentException("GSON cannot handle " + type);
// . . .
}
-
那么到誰是幕后操縱者呢慎璧?通過調(diào)試追蹤床嫌,最終定位到
ReflectiveTypeAdapterFactory
,從名稱上看就知道它用到了反射胸私。它有個嵌套類Adapter<T>
, 其read
方法源碼如下,可見實際上它的邏輯就是利用反射構(gòu)造一個對應(yīng)于T
的對象厌处,然后從讀取Json
字段,接著去T
對象中找到相應(yīng)的字段岁疼,并通過反射賦值過去阔涉。至于write
操作,原理是一致的捷绒,因此感興趣的童鞋可以查閱源碼瑰排。
@Override public T read(JsonReader in) throws IOException {
// . . .
T instance = constructor.construct();
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
field.read(in, instance);
}
// . . .
in.endObject();
return instance;
}
JsonParser
-
JsonParser
用于將json
數(shù)據(jù)轉(zhuǎn)換成JsonElement
對象,它的使用方法非常簡單暖侨,如下示例:
final String jsonStr = "{'data':'this is data', 'result':{'name':'horseLai', 'age':24}}";
JsonParser jsonParser = new JsonParser();
JsonObject jsonObject = jsonParser.parse(jsonStr).getAsJsonObject();
String data = jsonObject.get("data").getAsString();
JsonObject result = jsonObject.get("result").getAsJsonObject();
Data data1 = new Data();
data1.setData(data);
User user = new User();
user.setAge(result.get("age").getAsInt());
user.setName(result.get("name").getAsString());
data1.setResult(user);
//輸出:Data{data='this is data', result=User{name='horseLai', age=24}}
System.out.println(data1);
- 顯然椭住,直接使用
JsonParser
需要耗費很多體力,因為我們需要手動提取json
數(shù)據(jù)中的每一個字段屬性數(shù)據(jù)字逗,對于復(fù)雜度高的json
數(shù)據(jù)解析而言京郑,可以說相當(dāng)?shù)姆爆嵙恕?/li> -
那么
JsonParser
背后做了什么呢?- 我們已經(jīng)知道了
JsonParser#parse
會將json
數(shù)據(jù)解析成JsonElement
對象,那么結(jié)合之前對Gson#fromJson
方法的分析葫掉,我們可以肯定的是些举,它也會最終通過TypeAdaper#read
方法來從Json
數(shù)據(jù)流中讀取字段和值。 - 我們查看一下
JsonParser#parse
源碼俭厚,可以很容易的定位到以下源碼户魏,得知他最終是通過TypeAdapters.JSON_ELEMENT
來解析成JsonElement
,至此套腹,處理流程就與前面分析的一致了绪抛。
- 我們已經(jīng)知道了
public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException {
// . . .
return Streams.parse(json);
}
// Streams#parse
public static JsonElement parse(JsonReader reader) throws JsonParseException {
// . . .
reader.peek();
return TypeAdapters.JSON_ELEMENT.read(reader);
}
小結(jié)
- 至此,我們已經(jīng)分析了
Gson
直接將json
數(shù)據(jù)解析成java
實體類电禀,以及將java
實體類轉(zhuǎn)換成json
的處理流程幢码,以下是Gson#toJson
處理過程的總結(jié),對于Gson#fromJson
而言也是類似的尖飞。
Gson#toJson
-> 建立JsonWriter.AppendableWriter 數(shù)據(jù)流
-> 創(chuàng)建對應(yīng)于我們所需數(shù)據(jù)類型的TypeToken
-> 根據(jù)TypeToken 查找TypeToken緩存中是否已經(jīng)存在已經(jīng)對應(yīng)的使用過的TypeAdapter
-> 根據(jù)TypeToken 查找ThreadLocal是否存在對應(yīng)的使用過的TypeAdapter
-> 如果沒有症副,則遍歷所有TypeAdapterFactory并創(chuàng)建對應(yīng)于我們所需類型的TypeAdapter,
如果還沒匹配政基,那么也會ReflectiveTypeAdapterFactory贞铣,只要我們指定的類型是Java實體類的話,畢竟是通過反射操作沮明。
Gson中實現(xiàn)的TypeAdapter幾乎已經(jīng)覆蓋到了我們常用的所有類型辕坝,具體可查閱TypeAdapters類。
-> 在TypeAdapter.write中提取Java實體類字段值寫入到流中荐健,轉(zhuǎn)換成json數(shù)據(jù)