You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter/Dart中使用泛型封装REST API响应的优化方案

Flutter API响应统一处理优化方案

问题背景

我在Flutter中调用一个REST API,该API会返回三种不同的响应类型:

1. 包含单个数据的响应

{
  "data": {
        "amount": 3773.31
      },
      "responseId": "9167712d-f74b-412d-9677-95f7df54fa3a",
      "created": "2023-05-10T08:52:53.5394905Z",
      "status": {
        "code": 0,
        "message": "Operación completada con éxito",
        "isSuccess": true
      }
    }

2. 包含分页列表数据的响应

{
  "data": {
    "pageSize": 10,
    "currentPage": 1,
    "totalItems": 20,
    "totalPages": 2,
    "items": [
      {
        "id": 171631,
        "dateCreated": "2023-04-29T14:53:48.4233333",
        "cost": 0.28,
        "minutes": 300,
        "resource": 17
      },
      {
        "id": 171630,
        "dateCreated": "2023-04-29T14:37:32.6233333",
        "cost": 0.43,
        "minutes": 900,
        "resource": 17
      },
      {
        "id": 171629,
        "dateCreated": "2023-04-29T11:55:14.7733333",
        "cost": 0.23,
        "minutes": 120,
        "resource": 0
      },
      {
        "id": 171625,
        "dateCreated": "2023-04-29T10:20:05.3333333",
        "cost": 3.7,
        "minutes": 10800,
        "resource": 6
      },
      {
        "id": 171621,
        "dateCreated": "2023-04-29T09:06:57.4033333",
        "cost": 0.5,
        "minutes": 1200,
        "resource": 0
      },
      {
        "id": 171620,
        "dateCreated": "2023-04-29T08:59:16.5533333",
        "cost": 0.23,
        "minutes": 60,
        "resource": 0
      },
      {
        "id": 171617,
        "dateCreated": "2023-04-29T08:15:36.64",
        "cost": 0.23,
        "minutes": 60,
        "resource": 0
      },
      {
        "id": 171615,
        "dateCreated": "2023-04-29T07:29:29.6633333",
        "cost": 0.95,
        "minutes": 600,
        "resource": 0
      },
      {
        "id": 171614,
        "dateCreated": "2023-04-29T07:23:31.4966667",
        "cost": 0.58,
        "minutes": 300,
        "resource": 0
      },
      {
        "id": 171611,
        "dateCreated": "2023-04-28T17:55:30.0366667",
        "cost": 0.28,
        "minutes": 300,
        "resource": 0
      }
    ]
  },
  "responseId": "9fd49e0e-8543-4ad7-97ea-53d607c48f05",
  "created": "2023-05-10T08:53:29.382979Z",
  "status": {
    "code": 0,
    "message": "Operación completada con éxito",
    "isSuccess": true
  }
}

3. 无数据返回的响应

{
      "responseId": "a180068b-01f6-41b3-b19a-0ed2744d3a80",
      "created": "2023-05-10T08:54:17.7533949Z",
      "status": {
        "code": 0,
        "message": "Operación completada con éxito",
        "isSuccess": true
      }
    }

目前我通过以下代码处理响应,但每个返回列表的API都需要单独定义类似OrderListResponse的类,重复代码冗余,希望用泛型类型统一处理这类场景。

现有核心处理代码:

class ApiHelper 
           {

              final String apiUrl = "myapp.com";
            
              Future<dynamic> secureGet<T>(
                String url,
                FromJson<T> create,
              ) async {
                final String? token = await SecureStorage().getToken();
            
                Map<String, String> headers = {
                  HttpHeaders.contentTypeHeader: 'application/json',
                  HttpHeaders.acceptHeader: 'application/json',
                  HttpHeaders.authorizationHeader: 'Bearer $token',
                };
            
                Uri uri = Uri.https(
                  apiUrl,
                  url,
                );
            
                try {
                  final response = await http.get(uri, headers: headers);
                  return _returnResponse<T>(response, create);
                } on SocketException {
                  throw FetchDataException('Sin conexión');
                } on TimeoutException {
                  throw FetchDataException('Tiempo de espera agotado');
                } on Error catch (e) {
                  throw FetchDataException('Error general: $e');
                }
              }
        
        
    typedef FromJson<T> = T Function(Map<String, dynamic>);
    
    dynamic _returnResponse<T>(Response response, FromJson<T> create) {
      // Makes sure the HTTP request has ended with a 200 succes code
      switch (response.statusCode) {
        case 200:
          {
            // Get JSON object
            var data = json.decode(response.body);
    
            var apiResponse = ApiResponse<T>.fromJson(data, create);
    
            if (!apiResponse.status.isSuccess)
              throw ApiException(apiResponse.status.message);
    
            if (apiResponse.data != null) return create(apiResponse.data);
    
            return apiResponse.data;
          }
        case 400:
          throw BadRequestException(response.body);
        case 401:
        case 403:
          throw UnauthorisedException(response.body);
        case 500:
        case 404:
        default:
          throw FetchDataException(response.statusCode);
      }}
    
    class ApiResponse<T> {
          dynamic data;
          final APIStatus status;
        
          ApiResponse({
            required this.status,
            required this.data,
          });
        
          factory ApiResponse.fromJson(
                  Map<String, dynamic> json, Function(Map<String, dynamic>) create) =>
              new ApiResponse(
                status: APIStatus.fromJson(json["status"]),
                data: json['data'],
              );
        }
    
    
        class OrderListResponse {
          List<Order> results = [];
        
          OrderListResponse({required this.results});
        
          OrderListResponse.fromJson(Map<String, dynamic> json) {
            if (json['items'] != null) {
              json['items'].forEach((v) {
                results.add(new Order.fromJson(v));
              });
            }
          }
        }
}

优化方案

可以通过创建泛型ListResult<T>类统一处理分页列表响应,同时调整ApiResponse和解析逻辑,彻底消除重复代码。

1. 定义泛型分页列表实体

class ListResult<T> {
  final int pageSize;
  final int currentPage;
  final int totalItems;
  final int totalPages;
  final List<T> items;

  ListResult({
    required this.pageSize,
    required this.currentPage,
    required this.totalItems,
    required this.totalPages,
    required this.items,
  });

  factory ListResult.fromJson(
    Map<String, dynamic> json,
    T Function(Map<String, dynamic>) fromJsonItem,
  ) {
    var itemsList = json['items'] as List?;
    List<T> items = itemsList?.map((item) => fromJsonItem(item)).toList() ?? [];

    return ListResult(
      pageSize: json['pageSize'] as int,
      currentPage: json['currentPage'] as int,
      totalItems: json['totalItems'] as int,
      totalPages: json['totalPages'] as int,
      items: items,
    );
  }
}

2. 优化ApiResponse类

将data字段改为泛型类型,明确类型约束:

class ApiResponse<T> {
  final T? data;
  final APIStatus status;
  final String? responseId;
  final String? created;

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

  factory ApiResponse.fromJson(
    Map<String, dynamic> json,
    T? Function(Map<String, dynamic>)? fromJson,
  ) {
    T? parsedData;
    if (json.containsKey('data') && json['data'] != null && fromJson != null) {
      parsedData = fromJson(json['data'] as Map<String, dynamic>);
    }

    return ApiResponse(
      data: parsedData,
      status: APIStatus.fromJson(json["status"] as Map<String, dynamic>),
      responseId: json['responseId'] as String?,
      created: json['created'] as String?,
    );
  }
}

3. 调整_returnResponse方法

让解析逻辑适配三种响应场景,同时保证类型安全:

typedef FromJson<T> = T Function(Map<String, dynamic>);

dynamic _returnResponse<T>(Response response, FromJson<T>? create) {
  switch (response.statusCode) {
    case 200:
      final data = json.decode(response.body) as Map<String, dynamic>;
      final apiResponse = ApiResponse<T>.fromJson(data, create);

      if (!apiResponse.status.isSuccess) {
        throw ApiException(apiResponse.status.message);
      }

      return apiResponse.data;
    case 400:
      throw BadRequestException(response.body);
    case 401:
    case 403:
      throw UnauthorisedException(response.body);
    case 500:
    case 404:
    default:
      throw FetchDataException(response.statusCode.toString());
  }
}

4. 使用示例

  • 获取单个数据(如金额):
Future<Amount?> getAmount() async {
  return await secureGet<Amount>(
    '/api/amount',
    (json) => Amount.fromJson(json),
  );
}
  • 获取分页订单列表:
Future<ListResult<Order>?> getOrders() async {
  return await secureGet<ListResult<Order>>(
    '/api/orders',
    (json) => ListResult.fromJson(json, (itemJson) => Order.fromJson(itemJson)),
  );
}
  • 无数据的接口调用:
Future<void> submitAction() async {
  await secureGet<void>(
    '/api/submit',
    null, // 无需解析数据
  );
}

优化优势

  • 消除重复代码:所有分页列表都用ListResult<T>处理,无需为每个列表接口单独创建响应类。
  • 类型安全:泛型约束确保解析后的数据类型正确,避免运行时类型错误。
  • 扩展性强:新增其他响应结构时,只需添加对应泛型实体类,无需修改核心API处理逻辑。

内容的提问来源于stack exchange,提问作者Marc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 12:57:07