JSONとシリアル化
通信する必要のないモバイルアプリを考えるのは困難です。 Web サーバーに保存したり、ある時点で構造化データを簡単に保存したりできます。作るときは ネットワークに接続されたアプリの場合、古き良きアプリを消費する必要がある可能性があります。 JSON、遅かれ早かれ。
このガイドでは、Flutter で JSON を使用する方法について説明します。 さまざまなシナリオでどの JSON ソリューションを使用するか、およびその理由について説明します。
どの JSON シリアル化方法が私に適していますか?
この記事では、JSON を操作するための 2 つの一般的な戦略について説明します。
- 手動シリアル化
- コード生成を使用した自動シリアル化
プロジェクトが異なれば、複雑さや使用例も異なります。 小規模な概念実証プロジェクトや迅速なプロトタイプの場合は、 コード ジェネレーターを使用するのはやりすぎかもしれません。 より複雑な複数の JSON モデルを含むアプリの場合、 手動でエンコードすると、すぐに退屈で反復的な作業になる可能性があります。 そして、多くの小さなエラーが発生しやすくなります。
小規模なプロジェクトには手動シリアル化を使用する
手動 JSON デコードとは、組み込みの JSON デコーダを使用することを指します。dart:convert
。これには、生の JSON 文字列をjsonDecode()
関数を使用して、結果として必要な値を検索します。Map<String, dynamic>
。
外部依存関係や特定のセットアッププロセスはありません。
概念を簡単に実証するのに適しています。
プロジェクトが大きくなると、手動デコードはうまく機能しません。 デコード ロジックを手動で記述すると、管理が難しくなり、エラーが発生しやすくなります。 存在しない JSON にアクセスするときにタイプミスがあった場合 フィールドを使用すると、コードは実行時にエラーをスローします。
プロジェクトに JSON モデルがあまりなく、 コンセプトをすぐにテストしたいと考えているため、 手動シリアル化から始めることをお勧めします。 手動エンコードの例については、を参照してください。dart:convert を使用して手動で JSON をシリアル化する。
中規模から大規模のプロジェクトにはコード生成を使用する
コード生成による JSON シリアル化は、外部ライブラリを持つことを意味します
エンコード定型文を生成します。いくつかの初期設定を行った後、
モデルクラスからコードを生成するファイルウォッチャーを実行します。
例えば、json_serializable
とbuilt_value
これらは
ライブラリの種類。
このアプローチは、より大規模なプロジェクトに適しています。手書き禁止 ボイラープレートが必要であり、JSON フィールドにアクセスする際のタイプミスは次の場所で捕捉されます。 コンパイル時。コード生成の欠点は、いくつかの処理が必要なことです。 初期設定。また、生成されたソース ファイルによって視覚的に乱雑な状態が生じる可能性があります。 プロジェクトナビゲーターで。
がある場合は、JSON シリアル化に生成されたコードを使用したい場合があります。 中規模以上のプロジェクト。 JSON ベースのコード生成の例を確認するには エンコーディング、参照コード生成ライブラリを使用した JSON のシリアル化。
ジャクソン/ Flutter の Moshi に相当しますか?
GSONはありますか/単純な答えはノーです。
このようなライブラリにはランタイムを使用する必要があります反射では無効になっています。 ときめき。ランタイムリフレクションが干渉する木が揺れる、ダーツが持っている かなり長い間サポートされてきました。木を揺らすことで、使われていないものを「振り落とす」ことができます リリースビルドのコード。これにより、アプリのサイズが大幅に最適化されます。
リフレクションではデフォルトですべてのコードが暗黙的に使用されるため、ツリーが作成されます。 揺れるのが難しい。ツールは実行時にどの部分が未使用であるかを知ることができないため、 冗長なコードを削除するのは困難です。アプリのサイズは簡単に最適化できない 反射を使用する場合。
Flutterではランタイムリフレクションは使えませんが、 一部のライブラリは同様に使いやすい API を提供しますが、 代わりにコード生成に基づいています。これ アプローチについては、「」で詳しく説明されています。コード生成ライブラリセクション。
dart:convert を使用して手動で JSON をシリアル化する
Flutter での基本的な JSON シリアル化は非常に簡単です。 Flutterには組み込みがありますdart:convert
直接的な JSON エンコーダを含むライブラリと
デコーダ。
次のサンプル JSON は、単純なユーザー モデルを実装しています。
{
"name": "John Smith",
"email": "john@example.com"
}
とdart:convert
、
この JSON モデルは 2 つの方法でシリアル化できます。
JSONのインラインシリアル化
見ることで、dart:convert
ドキュメンテーション、
を呼び出すことで JSON をデコードできることがわかります。jsonDecode()
メソッドの引数として JSON 文字列を使用する関数。
Map<String, dynamic> user = jsonDecode(jsonString);
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
不幸にも、jsonDecode()
を返しますdynamic
、 意味
値の型は実行時まで分からないということです。このアプローチにより、
静的型付け言語の機能の大部分 (タイプ セーフ) が失われます。
オートコンプリート、そして最も重要なのはコンパイル時の例外です。あなたのコードは
即座にエラーが発生しやすくなります。
たとえば、にアクセスするたびに、name
またemail
フィールド、すぐにできます
タイプミスを紹介します。コンパイラが認識していないタイプミス
JSON はマップ構造内に存在します。
モデルクラス内での JSON のシリアル化
プレーンなモデルを導入することで、前述の問題に対処します。
クラスと呼ばれるUser
この例では。内部User
クラスでは、次のものが見つかります:
- あ
User.fromJson()
コンストラクター、新しいものを構築するためのUser
からのインスタンス マップ構造。 - あ
toJson()
メソッド。User
インスタンスをマップに追加します。
このアプローチでは、呼び出しコードタイプセーフティを持たせることができ、
のオートコンプリートname
とemail
フィールド、およびコンパイル時の例外。
タイプミスをした場合、またはフィールドを次のように扱った場合int
の代わりにString
さん、
アプリは実行時にクラッシュするのではなく、コンパイルされません。
ユーザーダーツ
class User {
final String name;
final String email;
User(this.name, this.email);
User.fromJson(Map<String, dynamic> json)
: name = json['name'],
email = json['email'];
Map<String, dynamic> toJson() => {
'name': name,
'email': email,
};
}
デコードロジックの責任がモデル内に移されました。 自体。この新しいアプローチを使用すると、ユーザーを簡単にデコードできます。
Map<String, dynamic> userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
ユーザーをエンコードするには、User
に反対するjsonEncode()
関数。
電話する必要はありませんtoJson()
メソッド、以来jsonEncode()
すでにあなたのためにやってくれています。
String json = jsonEncode(user);
このアプローチを使用すると、呼び出し元のコードは JSON を気にする必要がなくなります。
全然連載。ただし、モデル クラスは依然として確実にそうする必要があります。
実稼働アプリでは、シリアル化が確実に行われるようにする必要があります。
正しく動作します。実際には、User.fromJson()
とUser.toJson()
どちらのメソッドも、正しい動作を検証するために単体テストを実施する必要があります。
ただし、現実のシナリオは必ずしもそれほど単純ではありません。 場合によっては、JSON API 応答がより複雑になることがあります。 独自のモデルを通じて解析する必要があるネストされた JSON オブジェクトが含まれています クラス。
JSONエンコードを処理するものがあれば良いのですが そしてあなたのためにデコードします。幸いなことに、あります!
コード生成ライブラリを使用した JSON のシリアル化
他にも利用可能なライブラリがありますが、このガイドでは使用しますf310821d-c99e-4802-b9a0-9aecb44aa03、自動ソース コード ジェネレーター JSON シリアル化ボイラープレートを生成します。
シリアル化コードは手書きではないため、手動で保守されていないため これで、次の時点で JSON シリアル化例外が発生するリスクを最小限に抑えることができます。 ランタイム。
プロジェクトでの json_serializable の設定
含めるjson_serializable
プロジェクトでは、定期的に 1 つ必要です
依存性とその2開発の依存関係。要するに、開発の依存関係アプリのソース コードには含まれていない依存関係です。
開発環境でのみ使用されます。
依存関係を追加するには、次を実行します。flutter pub add
:
$ flutter pub add json_annotation dev:build_runner dev:json_serializable
走るflutter pub get
プロジェクトのルートフォルダー内
(またはクリックパッケージの取得エディタで)
これらの新しい依存関係をプロジェクトで利用できるようにします。
json_serializable の方法でモデル クラスを作成する
以下に変換方法を示します。User
クラスからjson_serializable
クラス。簡単にするために、
このコードは簡略化された JSON モデルを使用しています
前回のサンプルから。
ユーザーダーツ
import 'package:json_annotation/json_annotation.dart';
/// This allows the `User` class to access private members in
/// the generated file. The value for this is *.g.dart, where
/// the star denotes the source file name.
part 'user.g.dart';
/// An annotation for the code generator to know that this class needs the
/// JSON serialization logic to be generated.
@JsonSerializable()
class User {
User(this.name, this.email);
String name;
String email;
/// A necessary factory constructor for creating a new User instance
/// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
/// The constructor is named after the source class, in this case, User.
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
/// `toJson` is the convention for a class to declare support for serialization
/// to JSON. The implementation simply calls the private, generated
/// helper method `_$UserToJson`.
Map<String, dynamic> toJson() => _$UserToJson(this);
}
この設定では、ソース コード ジェネレーターはエンコード用のコードを生成します。
そしてデコードするname
とemail
JSON からのフィールド。
必要に応じて、命名戦略を簡単にカスタマイズすることもできます。
たとえば、API が次のようなオブジェクトを返した場合、ヘビの場合、
そしてあなたは使いたい小キャメルケースあなたのモデルでは、
を使用できます@JsonKey
name パラメータを使用した注釈:
/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;
サーバーとクライアントの両方が同じ命名戦略に従うことが最善です。
@JsonSerializable()
提供しますfieldRename
Dartを完全に変換するための列挙型
フィールドを JSON キーに変換します。
変更中@JsonSerializable(fieldRename: FieldRename.snake)
と同等です
追加する@JsonKey(name: '<snake_case>')
それぞれの分野へ。
サーバーのデータは不確実な場合があるため、データを検証して保護する必要がある
クライアント上で。
その他よく使用されるもの@JsonKey
注釈には次のものが含まれます。
/// Tell json_serializable to use "defaultValue" if the JSON doesn't
/// contain this key or if the value is `null`.
@JsonKey(defaultValue: false)
final bool isAdult;
/// When `true` tell json_serializable that JSON must contain the key,
/// If the key doesn't exist, an exception is thrown.
@JsonKey(required: true)
final String id;
/// When `true` tell json_serializable that generated code should
/// ignore this field completely.
@JsonKey(ignore: true)
final String verificationCode;
コード生成ユーティリティの実行
作成時json_serializable
初めての授業、
以下の画像に示されているようなエラーが発生します。
これらのエラーはまったく正常であり、単に生成されたコードが原因で発生します。 モデルクラスはまだ存在しません。これを解決するには、コードを実行します シリアル化ボイラープレートを生成するジェネレーター。
コード ジェネレーターを実行するには 2 つの方法があります。
ワンタイムコード生成
走ることでflutter pub run build_runner build --delete-conflicting-outputs
プロジェクトのルートで、
モデルが必要なときにはいつでも、モデルの JSON シリアル化コードを生成します。
これにより、ソース ファイルを介して 1 回限りのビルドがトリガーされ、
関連するものを選択し、それらに必要なシリアル化コードを生成します。
これは便利ですが、 モデルクラスに変更を加えるたびに手動でビルドします。
コードを継続的に生成する
あ監視者ソースコード生成プロセスがより便利になります。それ
プロジェクト ファイルの変更を監視し、必要なファイルを自動的に構築します。
必要に応じてファイルを保存します。実行してウォッチャーを開始しますflutter pub run build_runner watch --delete-conflicting-outputs
プロジェクトのルートにあります。
ウォッチャーを一度起動し、バックグラウンドで実行したままにしておくのが安全です。
json_serializable モデルの使用
JSON 文字列をデコードするには、json_serializable
道、
実際に以前のコードを変更する必要はありません。
Map<String, dynamic> userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
エンコードについても同様です。呼び出し側の API は以前と同じです。
String json = jsonEncode(user);
とjson_serializable
、
手動による JSON シリアル化は忘れて構いません。User
クラス。
ソース コード ジェネレーターは、次のファイルを作成します。user.g.dart
、
必要なシリアル化ロジックがすべて含まれています。
を確認するために自動テストを作成する必要はもうありません。
連載が機能するのは今です図書館の責任シリアル化が機能することを確認するため
適当に。
ネストされたクラスのコードの生成
クラス内にネストされたクラスを含むコードがある場合があります。
その場合、クラスを JSON 形式で渡そうとした場合
サービス (Firebase など) への引数として、
あなたも経験したことがあるかもしれませんInvalid argument
エラー。
次のことを考慮してくださいAddress
クラス:
import 'package:json_annotation/json_annotation.dart';
part 'address.g.dart';
@JsonSerializable()
class Address {
String street;
String city;
Address(this.street, this.city);
factory Address.fromJson(Map<String, dynamic> json) =>
_$AddressFromJson(json);
Map<String, dynamic> toJson() => _$AddressToJson(this);
}
のAddress
クラスは内部にネストされていますUser
クラス:
import 'package:json_annotation/json_annotation.dart';
import 'address.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
User(this.name, this.address);
String name;
Address address;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
ランニングflutter pub run build_runner build --delete-conflicting-outputs
ターミナルで作成します
の*.g.dart
ファイルですが、プライベート_$UserToJson()
関数
次のような感じになります。
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'name': instance.name,
'address': instance.address,
};
現時点ではすべて問題ないように見えますが、ユーザー オブジェクトに対して print() を実行すると次のようになります。
Address address = Address('My st.', 'New York');
User user = User('John', address);
print(user.toJson());
結果は次のとおりです。
{name: John, address: Instance of 'address'}
おそらく必要なものが次のように出力される場合:
{name: John, address: {street: My st., city: New York}}
これを機能させるには、次のように渡しますexplicitToJson: true
の中に@JsonSerializable()
クラス宣言の上にアノテーションを追加します。のUser
クラスは次のようになります。
import 'package:json_annotation/json_annotation.dart';
import 'address.dart';
part 'user.g.dart';
@JsonSerializable(explicitToJson: true)
class User {
User(this.name, this.address);
String name;
Address address;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
詳細については、を参照してください。explicitToJson
の中にJsonSerializable
のためのクラスjson_annotation
パッケージ。
さらなる参考文献
詳細については、次のリソースを参照してください。
- の
dart:convert
とJsonCodec
ドキュメンテーション - の
json_serializable
pub.dev のパッケージ - の
json_serializable
例GitHub 上で