theEUG
theEUG

Reputation: 409

How to map dynamic json api responses in dart

I have an API response payload with dynamic data in the body. The API returns a list of objects in the data tag. I am trying to map the response to the appropriate model at runtime, however, I get an error message when doing so. How do I map the dynamic response objects at runtime without explicitly creating an API response model for each? Ideally the solution should be able to determine the target model the response object should be mapped to at runtime.

I get the following error with my code: The argument type 'List<Map<String, dynamic>>' can't be assigned to the parameter type 'Map<String, dynamic>'.

Following my attempt:

return ApiResponse<Team>.fromJson(json.decode(response.body), (data) => Team.fromJson(data) as List<Map<String, dynamic>>);
// Maps the API response to object
class ApiResponse<T> {
  int status;
  String? message;
  T data;

  ApiResponse({
    required this.status,
    this.message,
    required this.data,
  });

  factory ApiResponse.fromJson(Map<String, dynamic> json, Function(List<Map<String, dynamic>>) create) {
    return ApiResponse<T>(
      status: json['status'],
      message: json['message'],
      data: create(json['data']),
    );
  }
}

My models


class User{
  int? id;
  String? name;
  String? description;
  DateTime? createdAt;
  DateTime? updatedAt;

  User({
    this.id,
    this.name,
    this.description,
    this.createdAt,
    this.updatedAt
  });

  factory User.fromJson(Map<String, dynamic> json){
    return User(
      id: json['id'],
      name: json['name'],
      description: json['description'],
      createdAt: DateTime.parse(json['created_at']),
      updatedAt: DateTime.parse(json['updated_at']),
    );
  }
}


class Team {
  int id;
  String? name;
  String? region;
  DateTime? createdAt;
  DateTime? updatedAt;

  Team({
    required this.id,
    this.name,
    this.region,
    this.createdAt,
    this.updatedAt,
  });

  factory Team.fromJson(Map<String, dynamic> json){
    return Team(
      id: json['id'],
      name: json['name'],
      region: json['region'],
      createdAt: DateTime.parse(json['created_at']),
      updatedAt: DateTime.parse(json['updated_at']),
    );
  }
}

API response


{
  "status": 200,
  "message": "Returned",
  "data": [
    {
      "id": 1,
      "name": "Trevo Mgino",
      "description": "A Description",
      "created_at": "2021-09-29T06:47:03.000000Z",
      "updated_at": "2021-09-29T06:47:03.000000Z"
    }
  ],
}
{
  "status": 200,
  "message": "Activated",
  "data": [
    {
      "id": 1,
      "name": "Team A",
      "region": "Region 1",
      "created_at": "2021-09-29T06:47:03.000000Z",
      "updated_at": "2021-09-29T06:47:03.000000Z"
    },
    {
      "id": 2,
      "name": "Team B",
      "region": "Region 1",
      "created_at": "2021-09-29T06:47:03.000000Z",
      "updated_at": "2021-09-29T06:47:03.000000Z"
    }
  ],
}

Upvotes: 2

Views: 2490

Answers (2)

theEUG
theEUG

Reputation: 409

So I managed to resolve the issues with the following code:

// Maps the API response to object
class ApiResponse<T> {
  int status;
  String? message;
  T data;

  ApiResponse({
    required this.status,
    this.message,
    required this.data,
  });

  factory ApiResponse.fromJson(Map<String, dynamic> json, List<T> Function(List<dynamic>) create) {
    return ApiResponse<T>(
      status: json['status'],
      message: json['message'],
      data: create(json['data']),
    );
  }
}

Calling the mapper

return ApiResponse<Team>.fromJson(json.decode(response.body),
      (data) => data.map((tData) => Team.fromJson(tData)).toList());

Upvotes: 0

Lulupointu
Lulupointu

Reputation: 3584

Team.fromJson takes a Map<String, dynamic> as a parameters but you give it a List<Map<String, dynamic>>.

I think what you want is to change the first expression you gave to:

ApiResponse<Team>.fromJson(
  json.decode(response.body),
  (data) => data.map((teamJson) => Team.fromJson(teamJson)),
)

Also you could type safe your factory be using:

class ApiResponse<T> {
  int status;
  String? message;
  T data;

  ApiResponse({
    required this.status,
    this.message,
    required this.data,
  });

  factory ApiResponse.fromJson(
    Map<String, dynamic> json,
    T Function(List<Map<String, dynamic>>) create,
  ) {
    return ApiResponse<T>(
      status: json['status'],
      message: json['message'],
      data: create(json['data']),
    );
  }
}

If you do so you will also have to change your first expression again to write the right type (I think you made a mistake but maybe I'm wrong):

ApiResponse<List<Team>>.fromJson(
  json.decode(response.body),
  (data) => data.map((teamJson) => Team.fromJson(teamJson)).toList(),
);

Upvotes: 2

Related Questions