【Flutter】JsonKeyチートシート

Dart

概要

どうも、@daiki1003です!

みなさま、Flutter生活はいかがでしょうか?

今回は、ある程度の規模感のアプリになると必須と言っても過言ではない
json_annotationパッケージに含まれているJsonKeyについての解説記事になります。

JsonKeyなにそれ?
・なんとなく使ってるけど知らないことがあるかも?

なんて人に役に立つような記事にしようと思っています。

それでは行ってみましょー!

執筆時環境

Flutter: 3.7.1
Dart: 2.19.1
json_annotation: 4.8.0

JsonKeyの概要

JsonKeyはその名の通り、JSONキーに対するエンコード/デコード
に関する細かい調整を行うためのアノテーションです。

JsonKeyは以下のようなクラス定義となっています。

@Target({TargetKind.field, TargetKind.getter})
class JsonKey {
  final Object? defaultValue;
  final bool? disallowNullValue;
  final Function? fromJson;
  final bool? includeFromJson;
  final bool? includeIfNull;
  final bool? includeToJson;
  final String? name;
  final Object? Function(Map, String)? readValue;
  final bool? required;
  final Function? toJson;
  final Enum? unknownEnumValue;

  const JsonKey({
    this.defaultValue,
    this.disallowNullValue,
    this.fromJson,
    this.includeFromJson,
    this.includeIfNull,
    this.includeToJson,
    this.name,
    this.readValue,
    this.required,
    this.toJson,
    this.unknownEnumValue,
  });

  static const Enum nullForUndefinedEnumValue = _NullAsDefault.value;
}

例えば、 @JsonKey(defaultValue: 1)のように指定することができます。
これ以降では、それぞれのプロパティに何が指定出来てその結果どうなるかについて詳しく解説していこうと思います。

各パラメタの説明

Object? defaultValue

JSONにキーが含まれていなかった場合の値を指定します。

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(defaultValue: 'defaultName')
    required String name,
  });
}

結果

// hoge.g.dart
_$Hoge _$$HogeFromJson(Map json) =>
    _$Hoge(
      name: json['name'] as String? ?? 'defaultName',
    );

また、 default"Value"とありますが、型が同じであれば、トップレベルの関数や引数を取らないコンストラクタも指定することができます。

String calculate() {
  return 'defaultName';
}

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(defaultValue: calculate)
    required String name,
  });
}

結果

// hoge.g.dart
_$Hoge _$$HogeFromJson(Map json) =>
    _$Hoge(
      name: json['name'] as String? ?? calculate(),
    );

bool? disallowNullValue

trueを指定した場合、JSONにキーがあるにも関わらず値がnullだった際に、DisallowedNullValueExceptionthrowします。

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(disallowNullValue: true)
    required String name,
  });
}

結果

_$_Hoge _$$_HogeFromJson(Map json) {
  $checkKeys(
    json,
    disallowNullValues: const ['name'],
  );
  return _$_Hoge(
    name: json['name'] as String,
  );
}

Function? fromJson/toJson

JSONからのエンコード/デコードの際に用いるメソッドです。
必ず一つの引数を取り、該当のプロパティの型を返してください。

DateTime fromTimestamp(int timestamp) {
  return DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
}

int toTimestamp(DateTime createdAt) {
  return createdAt.millisecondsSinceEpoch ~/ 1000;
}

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(fromJson: fromTimestamp, toJson: toTimestamp)
        required DateTime createdAt,
  });
}

結果

_$_Hoge _$$_HogeFromJson(Map json) =>
    _$_Hoge(
      createdAt: fromTimestamp(json['created_at'] as int),
    );

Map _$$_HogeToJson(_$_Hoge instance) =>
    {
      'created_at': toTimestamp(instance.createdAt),
    };

bool? includeFromJson/includeToJson

現在、最新である4.8.0で追加されたので情報が少なそうですね。
指定したプロパティをエンコード/デコードするかというフラグになります。
nullの際は、対象のプロパティがpublicならtrueprivateならfalseと同様の意味になります。

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(includeToJson: false)
        required String name,
  });
}

結果

_$_Hoge _$$_HogeFromJson(Map json) =>
    _$_Hoge(
      name: json['name'] as String? ?? '',
    );

// JSON化する際に、'name'を含まない
Map _$$_HogeToJson(_$_Hoge instance) =>
    {};

bool? includeIfNull

対象のプロパティがnullだった際に、JSONにキーを含めるかどうかを指定します。
includeIfNullnullの場合は、trueと同様です。

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(includeIfNull: false) String? name,
  });
}

結果

Map _$$_HogeToJson(_$_Hoge instance) {
  final val = {};

  void writeNotNull(String key, dynamic value) {
    if (value != null) {
      val[key] = value;
    }
  }

  writeNotNull('name', instance.name);
  return val;
}

String? name

未指定の場合、対象のプロパティと同名のキーをデコードし、同名のキーとしてエンコードします。
今までの例では、nameと言うプロパティを'name'と言うキーで扱っていました。
これを別名にしたい場合、nameを指定することで実現できます。

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(name: 'user_name') required String name,
  });
}

結果

_$_Hoge _$$_HogeFromJson(Map json) =>
    _$_Hoge(
      name: json['user_name'] as String?,
    );

Map _$$_HogeToJson(_$_Hoge instance) =>
    {
      'user_name': instance.name,
    };

Object? Function(Map, String)? readValue

json[key]以外で、プロパティのデコードを行いたい際に、使う関数を渡すことが出来ます。
あまり良い例ではないのですが、以下のような使い方ができると言うことです。

財布の中身が、カート内の合計金額より多ければ購入可能という判定をしています。

bool calculateCanBuy(Map json, String _) {
  final cartJson = json['cart'] as Map;
  final walletJson = json['wallet'] as Map;
  return (cartJson['current_price'] as int) <= (walletJson['money'] as int);
}

@freezed
class User with _$User {
  const factory User({
    @Default(false) @JsonKey(readValue: calculateCanBuy) bool canBuy,
    required String name,
    required Wallet wallet,
    required Cart cart,
  }) = _User;

  factory User.fromJson(Map json) => _$UserFromJson(json);
}

@freezed
class Wallet with _$Wallet {
  const factory Wallet({
    required int money,
  }) = _Wallet;

  factory Wallet.fromJson(Map json) => _$WalletFromJson(json);
}

@freezed
class Cart with _$Cart {
  const factory Cart({
    required int currentPrice,
  }) = _Cart;

  factory Cart.fromJson(Map json) => _$CartFromJson(json);
}

結果

_$_User _$$_UserFromJson(Map json) =>
    _$_User(
      canBuy: calculateCanBuy(json, 'can_buy') as bool? ?? false,
      ...
    );

bool? required

trueを指定すると、JSONからのデコードの際に該当のキーが含まれているかどうかをチェックします。
含まれていない場合は、MissingRequiredKeysExceptionthrowします。

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(required: true) required String name,
  });
}

結果

_$_Hoge _$$_HogeFromJson(Map json) {
  $checkKeys(
    json,
    requiredKeys: const ['name'],
  );
  return _$_Hoge(
    name: json['name'] as String,
  );
}

Enum? unknownEnumValue

対象プロパティがenumの際にのみ指定できます。
未知のenum値の時はここで指定した値としてデコードされます。

enum Sports {
  baseball,
  soccer,
  unknown,
}

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(unknownEnumValue: Sports.unknown) required Sports sports,
  });
}

結果

_$_Hoge _$$_HogeFromJson(Map json) =>
    _$_Hoge(
      sports: $enumDecode(_$SportsEnumMap, json['sports'],
          unknownValue: Sports.unknown),
    );

もし、未知のenum値の時は、nullにしておいてほしい場合は、JsonKey.nullForUndefinedEnumValueを指定します。

@freezed
class Hoge with _$Hoge {
  const factory Hoge({
    @JsonKey(unknownEnumValue: JsonKey.nullForUndefinedEnumValue)
        Sports? sports,
  });
}

結果

_$_Hoge _$$_HogeFromJson(Map json) =>
    _$_Hoge(
      sports: $enumDecodeNullable(_$SportsEnumMap, json['sports'],
          unknownValue: JsonKey.nullForUndefinedEnumValue),
    );

最後に

いかがでしたでしょうか?
一つでも知らなかった挙動やプロパティがあれば嬉しいなと思います!

JsonSerializableに関しても同様の記事を書こうかなと思っているのでお楽しみに!

誰かのお役に立てば。

Twitterフォローお願いします

「次回以降も記事を読んでみたい!」
「この辺分からなかったから質問したい!」

そんな時は、是非Twitter (@daiki1003)Instagram (@ashdik_flutter)のフォローお願いします♪

Twitterコミュニティ参加お願いします

Twitterコミュニティ「Flutter lovers」を開設しました!
参加お待ちしております😁

☕️ Buy me a coffee

また、記事がとても役に立ったと思う人は
コーヒーを奢っていただけると非常に嬉しいです!

コメント

タイトルとURLをコピーしました