溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么在Flutter上優(yōu)雅地序列化一個(gè)對(duì)象

發(fā)布時(shí)間:2022-01-11 16:52:42 來(lái)源:億速云 閱讀:135 作者:iii 欄目:開(kāi)發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“怎么在Flutter上優(yōu)雅地序列化一個(gè)對(duì)象”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“怎么在Flutter上優(yōu)雅地序列化一個(gè)對(duì)象”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來(lái)學(xué)習(xí)新知識(shí)吧。

序列化一個(gè)對(duì)象才是正經(jīng)事

對(duì)象的序列化反序列化是我們?nèi)粘>幋a中一個(gè)非?;A(chǔ)的需求,尤其是對(duì)一個(gè)對(duì)象的json encode/decode操作。每一個(gè)平臺(tái)都會(huì)有相關(guān)的庫(kù)來(lái)幫助開(kāi)發(fā)者方便得進(jìn)行這兩個(gè)操作,比如Java平臺(tái)上赫赫有名的GSON,阿里巴巴開(kāi)源的fastJson等等。

而在Flutter上,借助官方提供的JsonCodec,只能對(duì)primitive/Map/List這三種類型進(jìn)行json的encode/decode操作,對(duì)于復(fù)雜類型,JsonCodec提供了receiver/toEncodable兩個(gè)函數(shù)讓使用者手動(dòng)“打包”和“解包”。

顯然,JsonCodec提供的功能看起來(lái)相當(dāng)?shù)脑迹陂e魚(yú)app中存在著大量復(fù)雜對(duì)象序列化需求,如果使用這個(gè)類,就會(huì)出現(xiàn)集體“帶薪序列化”的盛況,而且還無(wú)法保證正確性。

官方推薦

機(jī)智如Google官方,當(dāng)然不會(huì)坐視不理。json_serializable的出現(xiàn)就是官方給出的推薦,它借助Dart Build System中的*buildrunner和json_annotation庫(kù),來(lái)自動(dòng)生成fromJson/toJson函數(shù)內(nèi)容。(關(guān)于使用build_runner*生成代碼的原理,之前興往同學(xué)的文章已經(jīng)有所提及)

關(guān)于如何使用json_serializable網(wǎng)上已經(jīng)有很多文章了,這里只簡(jiǎn)單提一些步驟:

  • Step 1 創(chuàng)建一個(gè)實(shí)體類。

  • Step 2 生成代碼:

讓build runner生成序列化代碼。運(yùn)行完成后文件夾下會(huì)出現(xiàn)一個(gè)xxx.g.dart文件,這個(gè)文件就是生成后的文件。

  • Step 3 代理實(shí)現(xiàn):

把fromJson和toJson操作代理給上面生成出來(lái)的類。

我們?yōu)槭裁床挥盟鼘?shí)現(xiàn)?

json_serializable完美實(shí)現(xiàn)了需求,但它也有不滿足需求的一面:

  • 使用起來(lái)有些繁瑣,多引入了一個(gè)類

  • 很重要的一點(diǎn)是,大量的使用"as"會(huì)給性能和最終產(chǎn)物大小產(chǎn)生不小的影響。實(shí)際上閑魚(yú)內(nèi)部的《flutter編碼規(guī)范》中,是不建議使用"as"的。(對(duì)包大小的影響可以參見(jiàn)三笠同學(xué)的文章,同時(shí)dart linter也對(duì)as的性能影響有所描述)

一種正經(jīng)的方式

基于上面的分析,很明顯的,需要一種新的方式來(lái)解決我們面臨的問(wèn)題,我們暫且叫它fish-serializable

1需要實(shí)現(xiàn)的功能

我們首先來(lái)梳理一下,一個(gè)序列化庫(kù)需要用到:

  1. 獲取可序列化對(duì)象的所有field以及它們的類型信息

  2. 能夠構(gòu)造出一個(gè)可序列化對(duì)象,并對(duì)它里面的fields賦值,且類型正確

  3. 支持自定義類型

  4. 最好能夠解決泛型的問(wèn)題,這會(huì)讓使用更加方便

  5. 最好能夠輕松得在不同的序列化/反序列化方式中切換,例如json和protobuf。

2困難在哪


  1. flutter禁用了dart:mirrors,反射API無(wú)法使用,也就無(wú)法通過(guò)反射的方式new一個(gè)instance、掃描class的fields。

  2. 泛型的問(wèn)題由于dart不進(jìn)行類型擦出,可以獲取,但泛型嵌套后依然無(wú)法解開(kāi)。

3Let's rock

無(wú)法使用dart:mirrors是個(gè)“硬”問(wèn)題,沒(méi)有反射的支持,類的內(nèi)容就是一個(gè)黑盒。于是我們?cè)谶~出第一步的時(shí)候就卡殼了- -!

這個(gè)時(shí)候筆者腦子里閃過(guò)了很多畫面,白駒過(guò)隙,烏飛兔走,啊,不是...是c++,c++作為一種無(wú)法使用反射的語(yǔ)言,它是如何實(shí)現(xiàn)對(duì)象的 序列化/反序列化 操作的呢?

一頓搜索猛如虎之后,發(fā)現(xiàn)大神們使用創(chuàng)建類對(duì)象的回調(diào)函數(shù)配合宏的方式來(lái)實(shí)現(xiàn)c++中類似反射這樣的操作。

這個(gè)時(shí)候,筆者又想到了曾經(jīng)朝夕相處的Android(現(xiàn)在已經(jīng)變成了flutter),Android中的Parcelable序列化協(xié)議就是一個(gè)很好的參照,它通過(guò)writeXXX APIs將類的數(shù)據(jù)寫入一個(gè)中間存儲(chǔ)進(jìn)行序列化,再通過(guò)readXXX APIs進(jìn)行反序列化,這就解決了我們上面提到的第一個(gè)問(wèn)題,既如何將一個(gè)類的“黑盒子”打開(kāi)。

同時(shí),Parcelable協(xié)議中還需要使用者提供一個(gè)叫做Creator的靜態(tài)內(nèi)部類,用來(lái)在反序列化的時(shí)候反射創(chuàng)建一個(gè)該類的對(duì)象或?qū)ο髷?shù)組,對(duì)于沒(méi)有反射可用的我們來(lái)說(shuō),用c++的那種回調(diào)函數(shù)的方式就可以完美解決反序列化中對(duì)象創(chuàng)建的問(wèn)題。

  • ValueHolder

  1. 這是一個(gè)數(shù)據(jù)中轉(zhuǎn)存儲(chǔ)的基類,它內(nèi)部的writeXXX APIs提供展開(kāi)類內(nèi)部的fields的能力,而readXXX則

  2. 用來(lái)將ValueHolder中的內(nèi)容讀取賦值給類的fields。


  3. readList/readMap/readSerializable函數(shù)中的type argument,我們把它作為外部想要解釋數(shù)據(jù)的

  4. 方式,比如readSerializable<T>(key: 'object'),表示外部想要把key為object的值解釋為T類

  5. 型。

  • FishSerializable

FishSerializable是一個(gè)interface,creator是個(gè)一個(gè)get函數(shù),用來(lái)返回一個(gè)“創(chuàng)建類對(duì)象的回調(diào)”,writeTo函數(shù)則用來(lái)在反序列化的時(shí)候放置ValueHoder->fields的代碼。
  • JsonSerializer

它繼承于FishSerializer接口,實(shí)現(xiàn)了encode/decode函數(shù),并額外提供encodeToMap和decodeFromMap功能。JsonSerializer類似JsonCodec,直接面向使用者用來(lái)json encode/decode

以上,我們已經(jīng)基本做好了一個(gè)flutter上支持對(duì)象序列化/反序列化操作的庫(kù)的基本架構(gòu)設(shè)計(jì),對(duì)象的序列化過(guò)程可以簡(jiǎn)化為:

怎么在Flutter上優(yōu)雅地序列化一個(gè)對(duì)象

由于ValueHolder中間存儲(chǔ)的存在,我們可以很方便得切換 序列化/反序列器,比如現(xiàn)有的JsonSerializer用來(lái)實(shí)現(xiàn)json的encode/decode,如果有類似protobuf的需求,我們則可以使用ProtoBufSerializer來(lái)將ValueHolder中的內(nèi)容轉(zhuǎn)換成我們需要的格式。

困難是不存在的

1如何匹配類型

為了能支持泛型容器的解析,我們需要類似下面這樣的邏輯:

  1. List<SerializableObject> list

  2.    = holder.readList<SerializableObject>(key: 'list');


  3. List<E> readList<E>({String key}){

  4.    List<dynamic> list = _read(key);

  5. }


  6. E _flattenList<E>(List<dynamic> list){

  7.    list?.map<E>((dynamic item){

  8.        // 比較E是否屬于某個(gè)類型,然后進(jìn)行對(duì)應(yīng)類型的轉(zhuǎn)換      

  9.    });

  10. }

在Java中,可以使用Class#isAssignableFrom,而在flutter中,我們沒(méi)有發(fā)現(xiàn)類似功能的API提供。而且,如果做下面這個(gè)測(cè)試,你還會(huì)發(fā)現(xiàn)一些很有意思的細(xì)節(jié):

  1. void main() {

  2.  print('int test');

  3.  test<int>(1);

  4.  print('\r\nint list test');

  5.  test<List<int>>(<int>[]);

  6.  print('\r\nobject test');

  7.  test<A<int>>(A<int>());

  8. }


  9. void test<T>(T t){

  10.  print(T);

  11.  print(t.runtimeType);

  12.  print(T == t.runtimeType);

  13.  print(identical(T, t.runtimeType));

  14. }


  15. class A<T>{


  16. }

輸出的結(jié)果是:

怎么在Flutter上優(yōu)雅地序列化一個(gè)對(duì)象

可以看到,對(duì)于List這樣的容器類型,函數(shù)的type argument與instance的runtimeType無(wú)法比較,當(dāng)然如果使用t is T,是可以返回正確的值的,但需要構(gòu)造大量的對(duì)象。所以基本上,我們無(wú)法進(jìn)行類型匹配然后做類型轉(zhuǎn)換。

2如何解析泛型嵌套

接下去就是如何分解泛型容器嵌套的問(wèn)題,考慮如下場(chǎng)景:

  1. Map<String, List<int>> listMap;


  2. listMap = holder.readMap<String, List<int>>(key: 'listMap');

readMap中得到的value type是一個(gè) List<int>,而我們沒(méi)有API去切割這個(gè)type argument。所以我們采用了一種比較“笨”也相對(duì)實(shí)用的方式。我們使用字符串切割了type argument,比如:

List<int> => <String>[List<int>, List, int]

然后在內(nèi)部展開(kāi)List或Map的時(shí)候,使用字符串匹配的方式匹配類型,在目前的使用中,完美得支持了標(biāo)準(zhǔn)List和Map容器互相嵌套。但目前無(wú)法支持標(biāo)準(zhǔn)List和Map之外的其他容器類型。

What's more

1IDE插件輔助

寫過(guò)Android的Parcelable的同學(xué)應(yīng)該有種很深刻的體會(huì),Parcelable協(xié)議中有大量的“機(jī)械”代碼需要寫,類似設(shè)計(jì)的fish-serializable也一樣。

為了不被老板和使用庫(kù)的同學(xué)打死,同時(shí)開(kāi)發(fā)了fish-serializable-intelij-plugin來(lái)自動(dòng)生成這些“機(jī)械”代碼。

2與json_serializable的對(duì)比


  • fish-serializable在使用上配合IDE插件,減少了大量的"as"操作符的使用,同時(shí)在步驟上也更加簡(jiǎn)短方便。

  • 相比于 json_annotation生成的代碼, fish-serializable生成的代碼也更具可讀性,方便手動(dòng)修改一些代碼實(shí)現(xiàn)。

  • fish-serializable可以通過(guò)手動(dòng)接管 序列化/反序列化 過(guò)程的方式完美兼容 json_annotation等其他方案。

讀到這里,這篇“怎么在Flutter上優(yōu)雅地序列化一個(gè)對(duì)象”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過(guò)才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI