Flutter json 2 model with Built Value

来源:https://www.cnblogs.com/mengdd/archive/2020/03/16/flutter-json-2-model-with-built-value.html
-Advertisement-
Play Games

Flutter中json轉換model, 除了手動轉之外, 就是利用第三方庫做一些代碼生成. 流行的庫有: [json_serializable](https://pub.dev/packages/json_serializable)和[built_value](https://pub.dev/p... ...


Flutter json 2 model with Built Value

Flutter中json轉換model, 除了手動轉之外, 就是利用第三方庫做一些代碼生成.
流行的庫有: json_serializablebuilt_value

本文介紹built_value的實際使用及問題處理.

Flutter中的json轉model方法

Flutter中json到model類型的轉換可以有多種方式:

  • 利用官方自帶的dart convert中的json解碼. 該方法只能將json轉換為List或Map, 剩下的工作需要手動完成, 根據key取值賦值給model的欄位.
  • 利用第三方的庫, 做代碼生成, 流行的庫有: json_serializablebuilt_value. 原理都是相同的, 先寫一些模板代碼, 說明一下model是什麼樣子的, 然後運行命令行生成一些代碼, 之後就可以很方便地調用, 將json轉換為model了.

使用json_serializable可以看:

本篇文章主要介紹built value的使用.

built value使用指南

實例: 用github api拿到的events: https://api.github.com/events?per_page=10
如何轉化成model對象呢?

TDD

先寫個測試, 明確一下我們想要的目標.

test下建立一個文件, 比如叫json_test.dart.

裡面寫main函數和兩個測試:

void main() {
  test("parse events list", () {
    const jsonString = """replace with events list json string""";

    expect(Event.fromEventsListJson(jsonString).first.id, "11732023561");
  });

  test("parse event", () {
    const jsonString = """replace with event json string""";

    expect(Event.fromJson(jsonString).id, "11732036753");
  });
}

這裡面應該放json字元串的, 太長了我就省略了, 這樣看比較清晰.

"""之後可以支持多行. (IDE裡面可以摺疊的.)

這個Event類和方法我們都還沒有寫, 所以暫時報錯.

setup

添加依賴, 去package頁面看添加什麼版本: https://pub.dev/packages/built_value

pubspec.yaml中添加:

dependencies:
  flutter:
    sdk: flutter

  # other dependencies here

  built_value: ^7.0.9
  built_collection: ^4.3.2

dev_dependencies:
  flutter_test:
    sdk: flutter

  # other dev_dependencies here

  build_runner: ^1.8.0
  built_value_generator: ^7.0.9

然後點Packages get.

Live Templates

這個是IntelliJ系IDE(包括Android Studio)的快捷設置, 目的是為了減少手動輸入. (可選.)

打開Preferences, 搜Live Templates.
Dart的部分點+號新增一個Live Template.

下麵Abbreviation選一個適當的縮寫, 比如built.
Template text貼入這段:

abstract class $CLASS_NAME$ implements Built<$CLASS_NAME$, $CLASS_NAME$Builder> {
  $CLASS_NAME$._();
  factory $CLASS_NAME$([void Function($CLASS_NAME$Builder) updates]) = _$$$CLASS_NAME$;
}

Applicable in Dart選: top-level.

建好之後以後就直接用啦.

建立models抽象類

輸入剛纔建立的live template的關鍵字built, 就會出現要生成的代碼, 其中寫好自己的類名.

比如我們要建立的model類型是Event類.
新建event.dart文件.

在其中輸入built按確認之後, 輸入類名Event, 就建好了:

abstract class Event implements Built<Event, EventBuilder> {
  Event._();
  factory Event([void Function(EventBuilder) updates]) = _$Event;
}

包括一個私有構造和一個工廠方法. 此時會有一些紅色的報錯.
這裡import 'package:built_value/built_value.dart'消除Built類的報錯.

根據觀察API: https://api.github.com/events 返回的json, 發現還應該有Actor, Repo, Payload三個類.
也都按這個方法建立好.

然後在其中添加欄位, 現在看起來是這樣了:

import 'package:built_value/built_value.dart';
// imports for models

part 'event.g.dart';

abstract class Event implements Built<Event, EventBuilder> {
  String get id;

  String get type;

  Actor get actor;

  Repo get repo;

  Payload get payload;

  bool get public;

  String get createdAt;

  Event._();

  factory Event([void Function(EventBuilder) updates]) = _$Event;
}

很重要的一步, 就是在類前面添加上一句: part 'event.g.dart';.
g.dart是一個慣例, 表明這個文件是生成的代碼. part表示目前這個文件是另一個文件的一部分.

按照同樣的方法把幾個類都建好.

註意如果有列表欄位, 要聲明為BuiltList類型.

運行生成命令

生成命令:

flutter packages pub run build_runner build

需要持續構建和可以用:

flutter packages pub run build_runner watch

這樣就不用每次改完代碼都需要跑一次命令了.

我們這裡用watch, 因為還沒有改完.
運行完成之後, 可以看到.g.dart的文件們都生成了, 報錯也消失了.

寫Serializers

新建文件serializers.dart.

import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';

// imports for models

part 'serializers.g.dart';

@SerializersFor(const [
  Event,
  Actor,
  Repo,
  Payload,
])
final Serializers serializers =
    (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();

@SerializersFor裡面列出想要序列化的類.

註意這裡要加上StandardJsonPlugin, 因為built value的json格式不是標準的, 而是所有欄位逗號分隔的.
用了StandardJsonPlugin之後就轉換成了標準的JSON格式.

因為我們跑命令的時候用的是watch, 所以保存修改後serializers.g.dart文件此時自動生成了.

添加model序列化和反序列化代碼

在Event類中添加:

  static Serializer<Event> get serializer => _$eventSerializer;

  String toJson() {
    return json.encode(serializers.serializeWith(Event.serializer, this));
  }

  static Event fromJson(String jsonString) {
    return serializers.deserializeWith(
        Event.serializer, json.decode(jsonString));
  }

import中除了model類還有:

import 'dart:convert';

import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'serializers.dart';

此時其他幾個model類也要添加serializer, 比如Actor類中添加:

static Serializer<Actor> get serializer => _$actorSerializer;

重新build生成代碼, 報錯消失.

現在可以運行測試:

  test("parse event", () {
    const jsonString = """replace with event json string""";

    expect(Event.fromJson(jsonString).id, "11732036753");
  });

來檢驗單個的Event model建立.

可能會遇到的失敗情況:

  • 一些欄位需要被標記為可為空@nullable.
  • 一些欄位名和key不匹配, 用@BuiltValueFieldwireName標記.
    詳見後面的Troubleshooting部分.

如何反序列化頂層列表?

Event的API返回的是一個Event的數組: []. 這種怎麼做呢?

這裡有個issue就是關於這個問題, 裡面的解決辦法挺好: https://github.com/google/built_value.dart/issues/565

serializers.dart中添加方法:

T deserialize<T>(dynamic value) =>
    serializers.deserializeWith<T>(serializers.serializerForType(T), value);

BuiltList<T> deserializeListOf<T>(dynamic value) => BuiltList.from(
    value.map((value) => deserialize<T>(value)).toList(growable: false));

其中BuiltList需要import 'package:built_collection/built_collection.dart';.

反序列化Event數組的方法:

  static List<Event> fromEventsListJson(String jsonString) {
    final BuiltList<Event> listOfEvents =
        deserializeListOf<Event>(json.decode(jsonString));
    return listOfEvents.toList();
  }

到這一步, 跑我們開頭寫的兩個測試應該都綠了. 如果沒綠見Troubleshooting部分.

泛型的fromJson方法.

上面給serializers中添加了兩個方法. 其中第一個方法是一個泛型的fromJson方法.

我們測試中的:

expect(Event.fromJson(jsonString).id, "11732036753");

也可以這樣寫:

expect(deserialize<Event>(json.decode(jsonString)).id, "11732036753");

這樣不用給每一個類都寫一個fromJson方法了.

Troubleshooting

可能會有的報錯, 問題原因和解決方式.

報錯1: failed due to: Invalid argument(s): Unknown type on deserialization. Need either specifiedType or discriminator field.

比如這個樣子:

Deserializing '[id, 11732036753, type, PushEvent, actor, {id: 54496419, login: supershell201...' to 'Event' failed due to: Invalid argument(s): Unknown type on deserialization. Need either specifiedType or discriminator field.

這是因為Event中依賴的類(Actor, Repo, Payload)沒有添加serializer.

比如Actor中:

static Serializer<Actor> get serializer => _$actorSerializer;

添加上重新build生成代碼即可.

報錯2: Tried to construct class "XXX" with null field

比如:

Deserializing '[id, 11732036753, type, PushEvent, actor, {id: 54496419, login: supershell201...' to 'Event' failed due to: Deserializing '[id, 54496419, login, supershell2019, display_login, supershell2019, gravatar...' to 'Actor' failed due to: Tried to construct class "Actor" with null field "displayLogin". This is forbidden; to allow it, mark "displayLogin" with @nullable.

此時, 先不要著急把欄位標記為@nullable.
而是要看這個欄位是否真的為null, 很有可能是因為欄位名稱和json中的key不匹配造成的, 比如json中是個蛇形命名.

查看了一下果然就是, 解決辦法:

  @BuiltValueField(wireName: 'display_login')
  String get displayLogin;

如果欄位真的是有可能為null的情況, 那麼加上@nullable:
比如:

  @BuiltValueField(wireName: 'ref_type')
  @nullable
  String get refType;

報錯3: FormatException: Control character in string

比如:

FormatException: Control character in string (at line 25, character 129)
... replica::on_client_write(dsn::message_ex *request, bool ignore_throttling)

相關issue: https://github.com/dart-lang/convert/issues/10

解決的辦法就是在測試的字元串聲明前加一個r:

const jsonString = r"""replace with events list json string""";

Model生成工具推薦

有個很棒的工具: https://charafau.github.io/json2builtvalue/
左邊輸入json字元串, 寫好命名, 點擊之後右邊就會出現那些本來需要手動寫的代碼.

生成的Event類是這樣:

library event;

import 'dart:convert';

import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';

part 'event.g.dart';

abstract class Event implements Built<Event, EventBuilder> {
  Event._();

  factory Event([updates(EventBuilder b)]) = _$Event;

  @BuiltValueField(wireName: 'id')
  String get id;
  @BuiltValueField(wireName: 'type')
  String get type;
  @BuiltValueField(wireName: 'actor')
  Actor get actor;
  @BuiltValueField(wireName: 'repo')
  Repo get repo;
  @BuiltValueField(wireName: 'payload')
  Payload get payload;
  @BuiltValueField(wireName: 'public')
  bool get public;
  @BuiltValueField(wireName: 'created_at')
  String get createdAt;
  String toJson() {
    return json.encode(serializers.serializeWith(Event.serializer, this));
  }

  static Event fromJson(String jsonString) {
    return serializers.deserializeWith(
        Event.serializer, json.decode(jsonString));
  }

  static Serializer<Event> get serializer => _$eventSerializer;
}

哈哈, 看到這裡是不是有種被騙了的感覺.

有了這個很棒的工具之後根本不用自己很小心地寫一個一個model類了, 只需要寫一個serializers.dart文件:

part 'serializers.g.dart';

@SerializersFor(const [
  Event,
  Actor,
  Repo,
  Payload,
])
final Serializers serializers =
    (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();

T deserialize<T>(dynamic value) =>
    serializers.deserializeWith<T>(serializers.serializerForType(T), value);

BuiltList<T> deserializeListOf<T>(dynamic value) => BuiltList.from(
    value.map((value) => deserialize<T>(value)).toList(growable: false));

然後把要反序列化的類加進來, 再跑命令行生成代碼, 就可以了.

經歷一下前面的手動過程可能理解得更好一些, 也知道各種問題的原因.
以後使用直接用工具就方便多了.

參考資料

  • 官方文檔: https://flutter.dev/docs/development/data-and-backend/json
  • Flutter實戰11.7: https://book.flutterchina.club/chapter11/json_model.html
  • built value github
  • 生成工具: https://charafau.github.io/json2builtvalue/

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文檔主要是用來解決物理備庫的性能監控問題。我們都知道,當物理備庫出現問題的時候,由於備庫是只讀模式的,所以無法在備庫上使用AWR/Statspack/ash/addm等性能報告來分析。因此,在故障分析和調優應用的時候,只能手動收集相關的統計數據信息,這樣給我們的工作帶來了巨大的不變。隨著Oracl ...
  • Clustered and Secondary Indexes(聚集索引和二級索引) Every InnoDB table has a special index called the clustered index where the data for the rows is stored. Ty ...
  • 如果應用程式遇到了下麵錯誤信息,那麼意味著連接池(connection pool)的連接數量由於一些原因導致其超過了Max Pool Size參數的限制。 英文錯誤信息: Timeout expired. The timeout period elapsed prior to obtaining a... ...
  • 在開發測試環境中,我們一般搭建Redis的單實例來應對開發測試需求,但是在生產環境,如果對可用性、可靠性要求較高,則需要引入Redis的集群方案。雖然現在各大雲平臺有提供緩存服務可以直接使用,但瞭解一下其背後的實現與原理總還是有些必要(比如面試), 本文就一起來學習一下Redis的幾種集群方案。 R ...
  • 註意:無特殊說明,Flutter版本及Dart版本如下: Flutter版本: 1.12.13+hotfix.5 Dart版本: 2.7.0 DatePicker Flutter並沒有DatePicker這個控制項,需要使用 方法彈出日期選擇控制項,基本用法如下: 初始化時間,通常情況下設置為當前時間。 ...
  • 新聞/News 1. "谷歌Pixel 4a將採用UFS 2.1存儲:可以體驗全套GMS" 1. "[圖]Android端Play商城現全面開放深色主題" 教程/Tutorial 1. "OkHttp Interceptor Making the most of it" 1. "
  • 推薦一款非常好用的 Mac系統清理工具 ...
  • 最近在學習Android Studio時,回顧了一些Java源碼,發現有些源碼點開以後找不到對應的真正代碼,如HashMap中的TreeNode是繼承自LinkedHashMap.LinkedHashMapEntry,但顯示找不見LinkedHashMapEntry這個靜態內部類,而且LinkedH ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...