How to reuse model as parent

Viewed 218

I get a json response in sample format like below

// call 1 : /all-users
{
  "success": true,
  "message": "All Users list",
  "data": [{
    "code": "USR001",
    "name": "User One"
  }]
}


// call 2 - /user-details/USR001
{
  "success": true,
  "message": "User Details of USR001",
  "data": {
    "code": "USR001",
    "firstName": "User",
    "lastName": "One",
    "email": "userOne@email.com",
  }
}

And my ApiResponse model is

class ApiResponse {
  bool success;
  String message;
  List<Data> data;

  ApiResponse({this.success, this.message, this.data});

  ApiResponse.fromJson(Map<String, dynamic> json) {
    success = json['success'];
    message = json['message'];
    if (json['data'] != null) {
      data = new List<Data>();
      json['data'].forEach((v) {
        data.add(new Data.fromJson(v));
      });
    }
  }
}

The data in the response may be an object or array depends on the call. I would like to reuse the ApiResponse model for both calls by extending the data as separate models. But i don't know how to do it.

2 Answers

I think the easiest way would be to simply have two classes that extend ApiResponse, and both can call your super.fromJson. Since data will differ, you can do one of two things:

  • Pull data out into the new derived classes, and parse it in their constructors separately
  • Turn ApiResponse into a class with generics (ApiResponse<T>, where data is of type T). You will also have to pass a function that can parse the data json if you go with this approach.

Personally, I think it is overkill to use generics for this, but I wanted to mention it as a possible solution. With that in mind, you could do something like the following:

class ApiResponseBase {
  bool success;
  String message;

  ApiResponse({this.success, this.message});

  ApiResponse.fromJson(Map<String, dynamic> json) {
    success = json['success'];
    message = json['message'];
  }
}

class ApiResponseArray extends ApiResponseBase {
  List<Data> data;

  ApiResponseArray.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
    if (json['data'] != null) {
      json['data'].forEach((v) {
        data.add(new Data.fromJson(v));
      });
    }
  }
}

class ApiResponseObject extends ApiResponseBase {
  Data data;

  ApiResponseObject.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
    if (json['data'] != null) {
      data = new Data.fromJson(json['data']);
    }
  }
}

Note how I removed data from the ApiResponseBase because it is now being parsed in the derived classes.

If you really dislike the fact that there are now two definitions of data, you could use generics without needing to increase complexity:

class ApiResponseBase<T> {
  bool success;
  String message;
  T data;
  // same as before 
}

class ApiResponseArray extends ApiResponseBase<List<Data>> {
  // same as before
}

class ApiResponseObject extends ApiResponseBase<Data> {
  // same as before
}

But it works either way.

Feel free to check my current ApiResponseModel:

// api_response_model.dart

class ApiResponseModel<T> {
  const ApiResponseModel({
    this.OK,
    this.data,
  });

  factory ApiResponseModel.fromJson(Map<String, dynamic> json) {
    return ApiResponseModel(
      OK: json['OK'],
      data: json['data'],
    );
  }

  final bool OK;
  final T data;
}


// avatar_repository.dart (use example)

final avatarResponse =
          ApiResponseModel<List<dynamic>>.fromJson(response.json()).data;

      final avatars = avatarResponse
          .map((avatar) => AvatarModel.fromJson(avatar))
          .toList();

// area_repository.dart (another example)

final area = ApiResponseModel<AreaModel>.fromJson(response.json()).data;

// user_repository.dart (complex cases)

final user = ApiResponseModel<dynamic>.fromJson(response.json()).data;
return UserModel.fromJson(user);
Related