【Flutter】json_serializableじゃなく、やっぱりfreezedが正解だった話

Dart

概要

どうも、@daiki1003です!

アプリを作っていく上で、通信の結果をjsonでシリアライズ/デシリアライズすると言う用件は
かなり出てくると思います。

そんな中で選択肢として出てくるのは json_serializablefreezed

この記事は
・この2つのうちどちらを最終的に選ぶべきかと言う悩みを持っている方
・freezedの強みって一体なんやねんと言う思いを持っている方

に向けた記事になるかなと思います。

僕は最初、json_serializableで行っていましたが
ある時にfreezedにして完全にこっちでええやんとなりました。

サマリ

json_serializableよりfreezedが優れていると思った点をざっくりと。
(freezedの絶対的価値と言うよりは、json_serializableと比較して)

・変数名の記述を2度しなくて良い
fromJsonのみの記述で良い (toJsonは自動生成)
toStringも自動生成 (これ地味に良い)
copyWithが自動で生えてくれる
・(当たり前っちゃ当たり前だが)getterのみしか生えない

それでは解説に行きます。

json_serializableでまずはやってみる

json_serializable | Dart package
Automatically generate code for converting to and from JSON by annotating Dart classes.

基本的な使い方は以下。

import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'person.g.dart';

@JsonSerializable(
  nullable: false,
  fieldRename: FieldRename.snake
)
class Person {
  const Person({
    required this.id,
    required this.phoneNumber,
  });

  factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
  Map< String, dynamic> toJson() => _$PersonToJson(this);

  final String id;
  final String phoneNumber;

  Person copyWith({String? id, String? phoneNumber}) {
    return Person(
      id: id ?? this.id, 
      phoneNumber: phoneNumber ?? this.phoneNumber,
    );
  }

  @override
  String toString() {
    return 'Person(id: $id, phoneNumber: $phoneNumber);
  }
}

これでbuild_runnerを実行すると以下の様なファイルができます。

// person.g.dart

part of 'person.dart';

Person _$PersonFromJson(Map<String, dynamic> json) {
  return Person(
    id: json['id'] as String,
    phoneNumber: json['phone_number'] as int,
  );
}

Map<String, dynamic> _$PersonToJson(Person instance) =>
    <String, dynamic>{
      'id': instance.id,
      'phone_number': instance.phoneNumber,
    };

json_serializableはjsonとのやりとり部分のみの上記ファイルを自動生成してくれます。

次にfreezedでやってみる

freezed | Dart package
Code generation for immutable classes that has a simple syntax/API without compromising on the features.

少し語弊はあるかもですが、freezedjson_serializableの拡張版と考えていただいても良いと思います。

import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'person.freezed.dart';
part 'person.g.dart';

@freezed
abstract class Person with _$Person {
  const factory Person({
    required String id,
    required String phoneNumber,
  }) = _Person;

  factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}

json_serializableと比較すると、少し記述に関しては癖が強いですが
記述量は明らかに減っています。

特にメンバ変数が増えてくると一層その差を強く感じることができます。

これを記述して、同じ様にbuild_runnerを走らせると様々なメソッドが実装された
person.freezed.dartperson.g.dartが生成されます。

json_serializableと比較してfreezedが優れていると思ういくつかの理由

変数名の記述を2度しなくて良い

上記を比較してもらうと、json_serializable
コンストラクタと変数宣言のため2度変数名を記述しています。

上記の例くらいであればそんなに問題はないですが
10個とか20個とか変数があるとこれが結構辛いです。

なお、freezedの例ではrequiredとしていますが
デフォルト値も以下の様に記述する事が出来ます。

const factory Person({
  @Default('') String id,
  @Default('000-0000-0000') String phoneNumber,
}) = _Person;

その際、build.yamlなどでjson_serializableのオプション値に
nullable: falseを指定していると失敗しますので注意が必要です。

fromJsonのみの記述で良い (toJsonは自動生成)

json_serializableは、言ってみれば「jsonとのやり取り部分は作るからあとはよろしく。」
と言う様なもんです。

なので、fromJson/toJsonの宣言、実装をこちら側でする必要があります。
それに対してfreezedはそのjsonとのやりとり部分まで一部自動化してくれます。

※とは言え、この辺はファイルテンプレートなりを作っていればそこまで大きな利点とはなりにくいですが。

toStringも自動生成 (これ地味に良い)

これはね、地味に最高です。

デバッグの際にちゃんとパース出来ているかを確認するために

print(person);

とやっても

Instance of 'Person'

としか、出てこず肝心の中身がみれません。

基本的にprintにカスタムクラスのインスタンスが渡されると、
toStringがoverrideされていなければ、上記の様にクラス名を表示するだけになっています。

変数の中身などをログ出力したい場合は、上記の例の様に
toStringをoverrideし、各メンバ変数を表示する様に実装しなければなりません。

これ結構大変なんです。。。

copyWithが自動で生えてくれる

この記事では詳細は省きますが、モデルクラスはimmutableが推奨されています。
となると、何か一つのメンバ変数を変更したい場合はその部分だけ変更した
インスタンスを作り直すことになります。

その際に使うのがcopyWithメソッドです。
引数は全てoptionalで、そのクラス自身が持つメンバ変数を基本全て受け取る事が出来る様にし、
渡されたものだけ上書きして新しいインスタンスを返すメソッドです。

Person copyWith({String id, String phoneNumber}) {
  return Person(
    id: id ?? this.id, 
    phoneNumber: phoneNumber ?? this.phoneNumber,
  );
}

これも変数が増えてくると地味に記述が大変なのですが、freezedであれば自動生成してくれます。

(当たり前っちゃ当たり前だが)getterのみしか生えない

上記の話とリンクはしてくるのですが、immutableにするためには
外部から変数を設定する事が出来てはいけません。

そのため、メンバ変数は読み込み専用にすべきですね。
json_serializableの例だと変数名に_がついておらず、setterが禁止されていないので
外部クラスから値を変更できてしまいます。

その他

いかがでしたでしょうか?
json_serializableに比べて、freezedの利点を理解していただけたでしょうか?

自分のプロジェクトでは結構な数をjson_serializableで進めてしまったので
粛々とfreezedに変更して行きたいと思います笑

誰かのお役に立てば。

Twitterフォローお願いします

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

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

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

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

☕️ Buy me a coffee

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

コメント

  1. […] 【Flutter】json_serializableじゃなく、やっぱりfreezedが正解だった話New […]

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