Flutter(Dart) 解析嵌套 JSON 为 model 对象的问题解决

416 记录 发表评论

最近在些 Flutter 应用时,用到了 GraphQL 分页,返回来的数据大概长下面这个样子,是按 Github API v4 的数据格式来整的:

{"users":{
     {
       "edges": [
         {
           "node": {
             "id": "1",
             "name": "ZhangSan" 
           },
           "cursor": "MA=="
         },
         {
           "node": {
             "id": "2",
             "name": "Lisi" 
           },
           "cursor": "MB=="
         }
       ],
       "pageInfo": {
         "hasNextPage": true,
         "hasPreviousPage": false,
         "startCursor": "MA==",
         "endCursor": "MB=="
       }
     }
 }

上面其实就是获取用户列表,这个列表在 GraphQL 里面有个专用的词语,叫 Connection ,它是嵌套的 JSON 格式数据,其中 node 里面就是实际的数据,比如这里是一个 User 对象,包含用户Id和用户名称。

在 flutter / dart 里面要使用这个数据,就要使用 fromJson 转换成实际的对象,也就是 model。

方案1 专用 Connection(可行,但重复代码多)

假设只有一个 User 需要 Pagination 分页,那就比较简单,直接这样创建一个 User 专用的 UserConnection

import 'package:termi/models/user.dart';

class UserConnection {
  UserConnection({
    this.edges,
    this.pageInfo,
  });

  List<Edge> edges;
  PageInfo pageInfo;

  factory UserConnection.fromJson(Map<String, dynamic> json) => UserConnection(
    edges: List<Edge>.from(json["edges"].map((x) => Edge.fromJson(x))),
    pageInfo: PageInfo.fromJson(json["pageInfo"]),
  );

  Map<String, dynamic> toJson() => {
    "edges": List<dynamic>.from(edges.map((x) => x.toJson())),
    "pageInfo": pageInfo.toJson(),
  };
}

class Edge {
  Edge({
    this.node,
    this.cursor,
  });

  User node;
  String cursor;

  factory Edge.fromJson(Map<String, dynamic> json) => Edge(
    node: User.fromJson(json["node"]),
    cursor: json["cursor"],
  );

  Map<String, dynamic> toJson() => {
    "node": node.toJson(),
    "cursor": cursor,
  };
}

class PageInfo {
  //...
}

这个方式 PageInfo 类可以公用,但是 ConnectionEdge 都必须每个类重新写一遍。

但是我们希望 Connection 是一个公共类,所有对象都能用,所以这是一个方案,但不是最佳的方案。

方案2 泛型(不可行)

如果改成泛型,那是不是就可以呢?如下:

class Connection<T> {
  //...

  factory UserConnection.fromJson(Map<String, dynamic> json) => UserConnection(
    edges: List<Edge<T>>.from(json["edges"].map((x) => Edge.fromJson(x))),
    //...
  );

  //...
}


class Edge<T> {
  //...

  T node;
  //...

  factory Edge.fromJson(Map<String, dynamic> json) => Edge(
    node: T.fromJson(json["node"]),
    //..
  );

  //...
}

这样是不可以的,因为泛型没有 fromJson 方法,编译报错。

方案3 接口方式(不可行)

有 Java 或其他类似语言开发经验可能会想到接口类。

先定义一个接口,在 Dart 用抽象类代替接口:

abstract class AbstractModel {
  factory AbstractModel.fromJson(Map<String, dynamic> json);
  Map<String, dynamic> toJson();
}

然后每个 model 对象实现这个抽象类,如 User

class User implements AbstractModel {
@override
factory User.fromJson(Map<String, dynamic> json) {
// TODO: implement toJson
}

@override
Map<String, dynamic> toJson() {
// TODO: implement toJson
}
}

Connection 里面,直接以接口类作为类型,泛型都不用:

//...

class Edge {
  //...

  AbstractModel node;
  //...

  factory Edge.fromJson(Map<String, dynamic> json) => Edge(
    node: AbstractModel.fromJson(json["node"]),
    //..
  );

  //...
}

可以看到,这里存在 2 个问题会产生问题:

  • 在抽象类中不能用 factory,它实际就是一个静态方法,不能放在抽象类中,所以这个抽象类就会报错。
  • 在 Edge 类中,我们用都是类都 fromJson,所以这里还是不能直接指定到实际类,比如 User 类的 fromJson,只能用 AbstractModel.fromJson,但是这个是抽象类,也不能调用。

所以,这个方案不可行。

方案4 传递函数方式(可行,推荐)

其实 Dart 的函数可以最为参数来传递, Edge 里面的 fromJson 和具体的 Model 类相关,那我们把函数传进来。

第1步,把 User 类的 fromJson 由 factory 改为静态方法:

static fromJson(Map<String, dynamic> json) => User(
  id: json["id"],
  name: json["name"],
);

第2步,ConnectionEdgefromJson 方法都多传递一个 nodeFromJson 参数,类型是 Function

class Connection {
  //...

  factory UserConnection.fromJson(Map<String, dynamic> json, Function nodeFromJson) => Connection(
    edges: List<Edge>.from(json["edges"].map((x) => Edge.fromJson(x, nodeFromJson))),
    //...
  );

  //...
}


class Edge {
  //...

  dynamic node;
  //...

  factory Edge.fromJson(Map<String, dynamic> json, , Function nodeFromJson) => Edge(
    node: nodeFromJson(json["node"]),
    //..
  );

  //...
}

第3步,使用

Connection connection = Connection.fromJson(data["users"], Question.fromJson);

bool hasNextPage = connection.pageInfo.hasNextPage;
List<User> users = connection.edges
    .map<User>((edge) => edge.node)
    .toList();

这样即能确保不会编写重复代码,用起来也很清晰明了。

参考资料:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

昵称 *