【入門編】Null許容型と「JSONシリアライズ」の境界線:fromJsonにおける安全なデフォルト値戦略 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは。Dartの深淵へようこそ。
Flutterでアプリを開発していると、必ず一度は頭を抱えるのが「外部APIから飛んでくるJSON」と「Dartの厳格なNull安全(Sound Null Safety)」の食い違いですよね。

「APIからは`null`が来るかもしれないのに、Dartのモデルクラスでは`String`として定義したい」。この境界線をどう安全に、かつエレガントに渡るか。今日はその極意を伝授します。

—

1. なぜ「外部」と「内部」で衝突するのか

DartのNull安全は、「実行時にNullPointerExceptionを起こさせない」という強い意志のもとに設計されています。しかし、外部APIはそうではありません。「フィールドが存在しない」あるいは「明示的に`null`が返る」ことは日常茶飯事です。

ここで、安易に`String?`(Null許容型)を乱発してはいけません。なぜなら、`String?`にすると、それを使うたびに`!(強制アンラップ)`や`if (val != null)`というノイズがコード全体に伝播してしまうからです。

「外部の曖昧さを、境界線(fromJson)で断ち切る」。 これがプロの設計です。

—

2. 実践:fromJsonにおける「安全なデフォルト値」戦略

`json_serializable`を使っているなら、`@JsonKey`アノテーションを使うのが最もスマートです。

基本的な書き方

import ‘package:json_annotation/json_annotation.dart’;

part ‘user.g.dart’;

@JsonSerializable()
class User {
// APIから値が来ない、あるいはnullの場合は空文字をデフォルトにする
@JsonKey(defaultValue: ”)
final String name;

// 数値の場合は0をデフォルトに
@JsonKey(defaultValue: 0)
final int age;

User({required this.name, required this.age});

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

このコードの何が「極限」なのか

通常、`fromJson`を手書きすると`json[‘name’] ?? ”`のようなロジックを書きますが、`@JsonKey(defaultValue: …)`を使うことで、「生成されるコード(`_g.dart`)」側にそのロジックを委譲しています。

これにより、ビジネスロジック層からは「`name`は絶対に`String`である」という強い保証が得られます。Dart VMは、このクラスがインスタンス化された瞬間に「Nullではない」ことを確約できるため、実行時の型チェックを最適化できるのです。

—

3. 「Null許容型」か「デフォルト値」か:判断の基準

現場で迷わないための、私なりの判断基準を共有します。

  • デフォルト値を入れるべきケース:
  • UIで「未設定」を表示する際に「空のラベル」や「0」で済む場合。
  • APIの仕様上、その項目が欠落していることが「異常」ではない場合。
  • Null許容型(`T?`)にするべきケース:
  • 「Nullであること」自体に特別な意味がある場合(例:`lastLoginDate`がNullなら「一度もログインしていない」)。
  • デフォルト値を決めると、UI側で「初期値なのか、本当にデータがないのか」の区別がつかなくなる場合。

—

4. 初学者が陥りやすい「罠」

罠1:`final`フィールドの初期化忘れ

Dartのコンストラクタで`this.name = …`と書く際、`defaultValue`を指定していても、コンストラクタ自体で値を代入しないと「初期化されていません」と怒られます。`json_serializable`はその生成コード内でうまく処理してくれますが、手書きする場合は注意が必要です。

罠2:型変換の失敗

APIから「文字列で数値」が送られてくることがあります(例:`”age”: “25”`)。
これを`int`型で受け取ろうとすると、Dartは型不一致でクラッシュします。この場合は、`@JsonKey(fromJson: …)`を使って、独自の変換関数を噛ませるのが正解です。

// 文字列を数値に変換する安全な関数
int _stringToInt(dynamic value) {
if (value is String) return int.tryParse(value) ?? 0;
return value as int? ?? 0;
}

@JsonKey(fromJson: _stringToInt)
final int age;

—

まとめ:境界線で「型」を確定させる

Dartにおいて「Null許容型」を放置することは、コードの「汚染」を放置することと同じです。

1. 境界線(fromJson)でデフォルト値を注入する。
2. ビジネスロジック内ではNullを排除し、非Null型として扱う。
3. どうしても必要な場合のみ、Null許容型を使う。

このルールを守るだけで、あなたの書くDartコードは劇的に堅牢になります。Null安全は、あなたを縛るためのものではなく、「実行時の予期せぬエラーから、あなたとユーザーを守るための強力な盾」なのです。

ここをクリアすれば、Dartの型システムとの距離はグッと縮まりますよ。ぜひ、今のプロジェクトで試してみてください!

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