0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter】JSONの連番キーオブジェクトをListで受け取る方法

Last updated at Posted at 2025-06-28

はじめに

データの一覧を取得するAPIで、配列ではなく連番のキーを用いたオブジェクトで返されるといった場面がありました。
私はFreezedRetrofitをもちいてAPIレスポンスの自動パースをしています。
MapオブジェクトからListに直接パースしたかったため、コンバーターを作成しました。

問題の状況

以下のような形式でレスポンスが返されます。

{
    "dataList": {
        "1": {"id": 1, "name": "Item 1"},
        "2": {"id": 2, "name": "Item 2"},
        ...
    }
}

dart側では以下のようなモデルをListで受け取る想定です。

@freezed
abstract class Model with _$Model {
  const factory Model({
    required int id,
    required String name,
  }) = _Model;
  factory Model.fromJson(Map<String, dynamic> json) =>
      _$ModelFromJson(json);
}

解決方法

無理やり変換するコンバーターを作成します

class DateListConverter
    implements JsonConverter<List<Model>, Map<String, dynamic>> {
  const DateListConverter();

  @override
  List<Model> fromJson(Map<String, dynamic> jsonMap) {
    // mapから要素を取り出す
    return jsonMap.values.map((value) {
      if (value is Map<String, dynamic>) {
        // modelのfromJson
        return Model.fromJson(value);
      }
      throw FormatException(
        'Expected a Map<String, dynamic> for value, but got ${value.runtimeType}',
      );
    }).toList(); // List化
  }

  @override
  Map<String, dynamic> toJson(List<Model> dataList) {
    final jsonMap = <String, dynamic>{};
    for (var i = 0; i < dataList.length; i++) {
      final item = dataList[i];
      // keyを指定
      final key = i;
      jsonMap[key] = toJsonT(item);
    }
    return jsonMap;
  }
}

以上のコンバーターを用いてレスポンスモデルを作ります。

@freezed
abstract class DataListResponseModel with _$DataListResponseModel {
  const factory DataListResponseModel({
    @DataListConverter() required List<Model> dataList,
  }) = _DataListResponseModel;
  factory DataListResponseModel.fromJson(Map<String, dynamic> json) =>
      _$DataListResponseModelFromJson(json);
}

このレスポンスモデルを用いてAPI通信メソッドを作ります(Retrofit)

@RestApi()
abstract class DataApiService {
  factory DataApiService(Dio dio) = _DataApiService;

  @GET('/data/list')
  Future<DataResponseModel> getDataList();
}

あとはレスポンスモデルからdataListを取り出せば完了です!

ベースクラスを作成して汎用化

以下のようにabstract classを用いて継承できるようにすると記述量を減らして使い回しやすくなります。

abstract class MapToListConverter<T>
    implements JsonConverter<List<T>, Map<String, dynamic>> {
  const MapToListConverter();

  /// Listに格納するオブジェクト(T)のfromJson
  T fromJsonT(Map<String, dynamic> json);

  /// Listに格納するオブジェクト(T)のtoJson
  Map<String, dynamic> toJsonT(T object);

  /// toJsonの際に使用するキーを決定する関数
  String keySelector(T object, int index);

  @override
  List<T> fromJson(Map<String, dynamic> jsonMap) {
    return jsonMap.values.map((value) {
      if (value is Map<String, dynamic>) {
        return fromJsonT(value);
      }
      throw FormatException(
        'Expected a Map<String, dynamic> for value, but got ${value.runtimeType}',
      );
    }).toList();
  }

  @override
  Map<String, dynamic> toJson(List<T> objectList) {
    final jsonMap = <String, dynamic>{};
    for (var i = 0; i < objectList.length; i++) {
      final item = objectList[i];
      final key = keySelector(item, i);
      jsonMap[key] = toJsonT(item);
    }
    return jsonMap;
  }
}
class DataListConverter extends MapToListConverter<Model> {
  const DataListConverter();

  @override
  Model fromJsonT(Map<String, dynamic> json) =>
      Model.fromJson(json);

  @override
  Map<String, dynamic> toJsonT(Model object) => object.toJson();

  @override
  String keySelector(Model object, int index) => index.toString();
}

おわりに

特殊なケースかもしれませんがretrofitの自動パースを活かしたかったため、このような対応を取りました。

もっと簡易的な方法があればコメントいただけるとありがたいです!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?