【入門編】Dartの「パターンマッチング」でJSONのバリデーションを型安全に行う手法 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!FlutterやDartでの開発を楽しんでいますか?

今回は、Dart 3で導入された「パターンマッチング(Pattern Matching)」を武器にして、外部からやってくるアヤシイJSONデータを安全かつエレガントに料理する方法についてお話ししますね。

ほら、Web APIや外部サービスからJSONを受け取るとき、「期待したキーが存在しなかった」「値が文字列のつもりなのに数値だった」といった理由で、アプリが突然クラッシュした経験はありませんか?

従来なら、大量の `if` 文や `is` キャストを書き連ねて、コードがスパゲッティのように複雑になっていましたよね。しかし、Dart 3のパターンマッチングを使いこなせば、コンパイラの型推論の恩恵を受けながら、まるでJSONの構造をそのままスケッチするかのように、美しく安全なバリデーションができるようになります。

ここをクリアすれば、あなたのDartコードの信頼性は一段と跳ね上がりますよ。一緒にマスターしていきましょう!

—

1. そもそもDart 3のパターンマッチングとは?

一言でいうと、パターンマッチングとは「データ構造の形(パターン)に、実際のデータが一致するかを検証しつつ、同時に中の値を取り出す仕組み」です。

イメージとしては、郵便局の「仕分けマシーン」を思い浮かべると分かりやすいかもしれません。
「この形(型や構造)の封筒だったら、このボックスに仕分けて、中の書類を取り出す」というルールを、コードで直感的に記述できるのです。

Dartでは、このパターンマッチングを `switch` 式や `if-case` 文の中で行えます。これがJSONのバリデーションにおいて、驚異的な威力を発揮するんですよね。

—

2. 従来のバリデーションが抱えていた闇

まずは、よくある「イケてない」JSON処理を見てみましょう。例えば、以下のようなユーザー情報のJSONを受け取るとします。

{
“id”: 42,
“name”: “Alice”,
“roles”: [“admin”, “user”]
}

これを従来のコードで安全に扱おうとすると、こうなります。

// 従来の辛いコード例
void handleUserData(Map json) {
final id = json[‘id’];
if (id is! int) {
throw FormatException(‘id is missing or not an int’);
}

final name = json[‘name’];
if (name is! String) {
throw FormatException(‘name is missing or not a String’);
}

final roles = json[‘roles’];
if (roles is! List || !roles.every((r) => r is String)) {
throw FormatException(‘roles is missing or invalid’);
}

// やっと処理できる…
print(‘User: $name (ID: $id), Roles: $roles’);
}

`if` と `is!` の嵐で、本当にやりたいビジネスロジックがどこにあるか見えなくなってしまっていますよね。メンテナンス性も最悪です。

—

3. パターンマッチングでJSONを鮮やかに解剖する

では、Dart 3の `switch` 式とパターンマッチングを使って、同じ処理を劇的にスマートに書き直してみましょう。

// Dart 3 パターンマッチングを活用したスマートなバリデーション
class User {
final int id;
final String name;
final List roles;

User({required this.id, required this.name, required this.roles});
}

User parseUser(Map json) {
// switch式を使ったパターンマッチング
return switch (json) {
// 【パターン】 id が int型、name が String型、roles が List型 であること
{
‘id’: int id,
‘name’: String name,
‘roles’: List roles,
} when roles.every((r) => r is String) =>
// マッチした瞬間に、それぞれの型として安全にバインドされる
User(id: id, name: name, roles: roles.cast()),

// 条件に一致しない場合のフォールバック(網羅性チェックが効くため安全)
_ => throw FormatException(‘無効なJSONフォーマットです: $json’),
};
}

コードのここがスゴい!

1. 構造の直感的な一致(Map Pattern)
`{‘id’: int id, …}` という記述は、「Mapの中に `’id’` というキーがあり、その値が `int` 型であれば、変数 `id` に代入せよ」という宣言です。JSONの見た目とコードの形がほぼ一致していますよね。
2. ガード節(`when` 句)による追加検証
リストの要素がすべて `String` であるかという細かい条件も、`when` 句を使って自然に組み込めます。
3. 網羅性(Exhaustiveness)の担保
Dartのコンパイラは、すべてのパターンが網羅されているかをチェックします。`_`(ワイルドカード)によるデフォルトケースを用意することで、予期せぬデータの混入を確実に検知できます。

—

4. 陥りやすい文法エラーと注意点

パターンマッチングを使い始めるときに、多くの開発者がハマりがちなポイントをいくつかシェアしておきますね。

① キャストの罠(`List` から `List` への変換)

JSONのパースでは、リストは通常 `List` としてデコードされます。パターンマッチングで `List roles` と書いても、実行時のジェネリック型の実態は `List` のままです。
そのため、上記のコード例のように `.cast()` や `.map((e) => e as String).toList()` を挟む必要がある点に注意してください。

② キーの欠損と `null` の扱い

もしJSONに `name` キー自体が含まれていなかった場合、上記のパターンはマッチしません(エラーになります)。
もし「キーがなくてもデフォルト値を設定したい」「nullを許容したい」という場合は、パターン側で明示的にオプショナル型を扱う必要があります。

// 例: middleName がなくても許容する場合
{
‘id’: int id,
‘name’: String name,
‘middleName’: String? middleName, // null や String にマッチ
}

—

まとめ

いかがでしょうか?

Dart 3のパターンマッチングを使えば、外部からやってくる信頼性の低いJSONデータであっても、「型安全かつ宣言的」にバリデーションとオブジェクトへの変換を同時に行うことができます。

  • 複雑な `if / is` チェックの山から解放される
  • コードの見た目がJSONの構造と直結するため、可読性が爆発的に上がる
  • コンパイラの強力な型推論と網羅性チェックの恩恵を受けられる

ここをマスターすれば、あなたの書くDartコードはワンランク上の洗練されたものになりますよ。ぜひ、実際のプロジェクトのAPIクライアント層などで試してみてくださいね。

それでは、次回の記事もお楽しみに!バッチリ使いこなしていきましょう!

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