<

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_serializablebuilt_valueこれらは ライブラリの種類。

このアプローチは、より大規模なプロジェクトに適しています。手書き禁止 ボイラープレートが必要であり、JSON フィールドにアクセスする際のタイプミスは次の場所で捕捉されます。 コンパイル時。コード生成の欠点は、いくつかの処理が必要なことです。 初期設定。また、生成されたソース ファイルによって視覚的に乱雑な状態が生じる可能性があります。 プロジェクトナビゲーターで。

がある場合は、JSON シリアル化に生成されたコードを使用したい場合があります。 中規模以上のプロジェクト。 JSON ベースのコード生成の例を確認するには エンコーディング、参照コード生成ライブラリを使用した JSON のシリアル化

GSONはありますか/ジャクソン/Flutter の Moshi に相当しますか?

単純な答えはノーです。

このようなライブラリにはランタイムを使用する必要があります反射では無効になっています。 ときめき。ランタイムリフレクションが干渉する木が揺れる、ダーツが持っている かなり長い間サポートされてきました。木を揺らすことで、使われていないものを「振り落とす」ことができます リリースビルドのコード。これにより、アプリのサイズが大幅に最適化されます。

リフレクションではデフォルトですべてのコードが暗黙的に使用されるため、ツリーが作成されます。 揺れるのが難しい。ツールは実行時にどの部分が未使用であるかを知ることができないため、 冗長なコードを削除するのは困難です。アプリのサイズは簡単に最適化できない 反射を使用する場合。

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インスタンスをマップに追加します。

このアプローチでは、呼び出しコードタイプセーフティを持たせることができ、 のオートコンプリートnameemailフィールド、およびコンパイル時の例外。 タイプミスをした場合、またはフィールドを次のように扱った場合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);
}

この設定では、ソース コード ジェネレーターはエンコード用のコードを生成します。 そしてデコードするnameemailJSON からのフィールド。

必要に応じて、命名戦略を簡単にカスタマイズすることもできます。 たとえば、API が次のようなオブジェクトを返した場合、ヘビの場合、 そしてあなたは使いたい小キャメルケースあなたのモデルでは、 を使用できます@JsonKeyname パラメータを使用した注釈:

/// Tell json_serializable that "registration_date_millis" should be
/// mapped to this property.
@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;

サーバーとクライアントの両方が同じ命名戦略に従うことが最善です。
@JsonSerializable()提供しますfieldRenameDartを完全に変換するための列挙型 フィールドを 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初めての授業、 以下の画像に示されているようなエラーが発生します。

IDE warning when the generated code for a model class does not exist
yet.

これらのエラーはまったく正常であり、単に生成されたコードが原因で発生します。 モデルクラスはまだ存在しません。これを解決するには、コードを実行します シリアル化ボイラープレートを生成するジェネレーター。

コード ジェネレーターを実行するには 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:convertJsonCodecドキュメンテーション
  • json_serializablepub.dev のパッケージ
  • json_serializable例GitHub 上で