🎃

DartでAPIの値が0やnullになるときの対処法(カスタムJsonConverterの作り方)

に公開

🎯 この記事でわかること

  • APIから 0null が返ってくるときに、Dart側でどう処理するか
  • @JsonConverter を使ったカスタムコンバータの作成方法
  • DartPadで試せるシンプルなサンプルコード付き!

🧩 背景:APIのレスポンスが0ってどういうこと?

ある日、APIから以下のようなデータが返ってきたとします:

{
  "user": 0
}

この user は本来オブジェクト(たとえば {"id": 1, "name": "Alice"})が返ってくるべき項目ですが、0nullfalse といった値が返されてしまうことがあります。これはAPI側の都合や状態管理の仕様によるもので、サーバー側での統一的な対応が難しい場合もあるため、クライアント側で柔軟に処理する必要があります。

これをそのまま User クラスにマッピングしようとすると、クラッシュしてしまいます。

💡 解決策:カスタムJsonConverterを使おう

Dartの json_serializable パッケージには @JsonConverter という便利な機能があります。

これを使えば、データの変換ルールを自分で書くことができます。

🧪 サンプルコード(DartPad対応)

import 'dart:convert';

/// ユーザーデータを表すクラス
class User {
  /// ユーザーID
  final int id;

  /// ユーザー名
  final String name;

  /// コンストラクタ(Userクラスのインスタンスを作る)
  User({required this.id, required this.name});

  /// JSON(Map形式)からUserインスタンスを生成するファクトリーメソッド
  /// 
  /// [json] - ユーザー情報のMap(例:{'id': 1, 'name': 'Alice'})
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
    );
  }

  /// UserインスタンスをMap形式のJSONに変換する
  Map<String, dynamic> toJson() => {
        'id': id,
        'name': name,
      };
}

/// APIのユーザー情報をUserインスタンスに変換するカスタムコンバータ
class UserConverter {
  /// JSONデータからUserインスタンスを生成する
  ///
  /// [json] - ユーザー情報のJSON(Map形式 または 0 / null の場合あり)
  /// 
  /// - 通常はMap(例:{'id': 1, 'name': 'Alice'})
  /// - 0やnullが入ってくる場合(例:{'user': 0} または {'user': null})はnullを返す
  static User? fromJson(dynamic json) {
    if (json is Map<String, dynamic>) {
      return User.fromJson(json);
    }
    // jsonがMap以外(0やnull)の場合はnullを返す
    return null;
  }

  /// UserインスタンスをJSON形式に変換する
  ///
  /// [user] - 変換したいUserインスタンス。nullの場合も対応。
  static dynamic toJson(User? user) {
    return user?.toJson(); // userがnullならnullを返す
  }
}

void main() {
  // パターン1:正常なユーザー情報が返ってきた場合
  const json1 = '{"user": {"id": 1, "name": "Alice"}}';
  final map1 = json.decode(json1) as Map<String, dynamic>; // JSON文字列をMapに変換
  final user1 = UserConverter.fromJson(map1['user']); // 'user'部分をUserに変換
  print('User1: ${user1?.name}'); // => Alice(ユーザー名を出力)

  // パターン2:APIから0が返ってきた場合(例:休会中などのケース)
  const json2 = '{"user": 0}';
  final map2 = json.decode(json2) as Map<String, dynamic>;
  final user2 = UserConverter.fromJson(map2['user']);
  print('User2: ${user2?.name ?? "null"}'); // => null(nullを出力)

  // パターン3:APIからnullが返ってきた場合(ユーザー情報が存在しない)
  const json3 = '{"user": null}';
  final map3 = json.decode(json3) as Map<String, dynamic>;
  final user3 = UserConverter.fromJson(map3['user']);
  print('User3: ${user3?.name ?? "null"}'); // => null(nullを出力)
}

🔄 @JsonConverterを使った変換処理

Dartの json_serializable パッケージには @JsonConverter という便利な機能があります。

これを使えば、データの変換ルールを自分で書くことができます。

Flutterで json_serializable を使っている場合は、以下のように @JsonKey@JsonConverter を使います。

import 'package:json_annotation/json_annotation.dart';

part 'sample_response.g.dart';

()
class SampleResponse {
  () // ← ここでカスタムコンバータを指定しています!
  final User? user;

  SampleResponse({this.user});

  factory SampleResponse.fromJson(Map<String, dynamic> json) =>
      _$SampleResponseFromJson(json);

  Map<String, dynamic> toJson() => _$SampleResponseToJson(this);
}

class User {
  final int id;
  final String name;

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

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
    );
  }

  Map<String, dynamic> toJson() => {
        'id': id,
        'name': name,
      };
}

/// ★★★★★ ここがカスタムコンバータ! ★★★★★
class UserDtoConverter implements JsonConverter<User?, Object?> {
  /// 定数コンストラクタ(アノテーションで使うため)
  const UserDtoConverter();

  /// JSONからUser?に変換
  /// - MapだったらUser.fromJsonを呼ぶ
  /// - 0やnullならnullを返す
  
  User? fromJson(Object? json) {
    if (json is Map<String, dynamic>) {
      return User.fromJson(json);
    }
    return null;
  }

  /// User?からJSONに変換(nullならnullを返す)
  
  Object? toJson(User? object) => object?.toJson();
}

✅ まとめ

  • APIの仕様によって 0null が返るケースでは、クラッシュを避けるための変換処理が必要
  • JsonConverter や独自の変換ロジックを使うことで、安全にデータを取り扱える

🔗 関連リンク


このようなパターンはAPIの設計ミスに近いですが、現場ではよくあります😅
ぜひこのパターンを覚えて、安全で柔軟なデコード処理を実装していきましょう!

Discussion