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

Flutter如何反序列化包含两个列表的JSON对象并基于共同ID关联数据?

Flutter如何反序列化包含两个列表的JSON对象并基于共同ID关联数据?

看起来你现在的代码是分开两次请求获取data和included列表,但这样不仅会重复发起网络请求浪费资源,还没解决核心的关联数据问题。你的JSON结构符合JSON:API规范,data里的帖子关联了用户、投票等实体,included里是这些关联实体的详情,我们可以通过一次请求拿到完整数据,再把两者关联起来。

下面是完整的解决方案:

第一步:创建对应的模型类

首先我们需要为JSON里的各个层级创建模型类,每个类包含fromJson构造函数来完成反序列化。这里我列出核心的几个类,你可以根据需要补充其他字段:

Attributes(帖子属性类)

class Attributes {
  final String message;
  final String visibility;
  final String createdAt;
  final String updatedAt;
  final dynamic metaInfo;
  final String type;
  final int likesCount;
  final int repliesCount;
  final List<dynamic> widgetsData;
  final bool isLiked;
  final bool isBookmarked;
  final bool isDeleted;
  final dynamic shareCount;

  Attributes({
    required this.message,
    required this.visibility,
    required this.createdAt,
    required this.updatedAt,
    required this.metaInfo,
    required this.type,
    required this.likesCount,
    required this.repliesCount,
    required this.widgetsData,
    required this.isLiked,
    required this.isBookmarked,
    required this.isDeleted,
    required this.shareCount,
  });

  factory Attributes.fromJson(Map<String, dynamic> json) {
    return Attributes(
      message: json['message'],
      visibility: json['visibility'],
      createdAt: json['created_at'],
      updatedAt: json['updated_at'],
      metaInfo: json['meta_info'],
      type: json['type'],
      likesCount: json['likes_count'],
      repliesCount: json['replies_count'],
      widgetsData: json['widgets_data'] ?? [],
      isLiked: json['is_liked'],
      isBookmarked: json['is_bookmarked'],
      isDeleted: json['is_deleted'],
      shareCount: json['share_count'],
    );
  }
}

关联关系相关类

处理帖子和其他实体的关联:

// 通用的关联数据结构
class RelatedData {
  final String id;
  final String type;

  RelatedData({required this.id, required this.type});

  factory RelatedData.fromJson(Map<String, dynamic> json) {
    return RelatedData(
      id: json['id'],
      type: json['type'],
    );
  }
}

// 帖子的关联关系总类
class Relationships {
  final RelatedData? user;
  final RelatedData? poll;
  final List<RelatedData> taggedUsers;
  // 其他关联字段可以根据JSON补充
  final List<dynamic> taggedCompanies;
  final List<dynamic> taggedTopics;

  Relationships({
    required this.user,
    required this.poll,
    required this.taggedUsers,
    required this.taggedCompanies,
    required this.taggedTopics,
  });

  factory Relationships.fromJson(Map<String, dynamic> json) {
    return Relationships(
      user: json['user']['data'] != null ? RelatedData.fromJson(json['user']['data']) : null,
      poll: json['poll']['data'] != null ? RelatedData.fromJson(json['poll']['data']) : null,
      taggedUsers: (json['tagged_users']['data'] as List).map((e) => RelatedData.fromJson(e)).toList(),
      taggedCompanies: json['tagged_companies']['data'] ?? [],
      taggedTopics: json['tagged_topics']['data'] ?? [],
    );
  }
}

帖子主类(Datum)

这里我们额外添加字段来存储关联的实体详情:

class Datum {
  final String id;
  final String type;
  final Attributes attributes;
  final Relationships relationships;
  // 存储关联的实体详情
  IncludedUser? postUser;
  IncludedPoll? postPoll;
  List<IncludedUser>? taggedUsersList;

  Datum({
    required this.id,
    required this.type,
    required this.attributes,
    required this.relationships,
    this.postUser,
    this.postPoll,
    this.taggedUsersList,
  });

  factory Datum.fromJson(Map<String, dynamic> json) {
    return Datum(
      id: json['id'],
      type: json['type'],
      attributes: Attributes.fromJson(json['attributes']),
      relationships: Relationships.fromJson(json['relationships']),
    );
  }
}

Included里的实体类

处理included列表中的用户和投票:

// 用户实体
class IncludedUser {
  final String id;
  final String type;
  final UserAttributes attributes;

  IncludedUser({
    required this.id,
    required this.type,
    required this.attributes,
  });

  factory IncludedUser.fromJson(Map<String, dynamic> json) {
    return IncludedUser(
      id: json['id'],
      type: json['type'],
      attributes: UserAttributes.fromJson(json['attributes']),
    );
  }
}

class UserAttributes {
  final String displayName;
  final String username;
  final String dpThumbnail;
  final String marketView;

  UserAttributes({
    required this.displayName,
    required this.username,
    required this.dpThumbnail,
    required this.marketView,
  });

  factory UserAttributes.fromJson(Map<String, dynamic> json) {
    return UserAttributes(
      displayName: json['display_name'],
      username: json['username'],
      dpThumbnail: json['dp_thumbnail'],
      marketView: json['market_view'],
    );
  }
}

// 投票实体
class IncludedPoll {
  final String id;
  final String type;
  final PollAttributes attributes;

  IncludedPoll({
    required this.id,
    required this.type,
    required this.attributes,
  });

  factory IncludedPoll.fromJson(Map<String, dynamic> json) {
    return IncludedPoll(
      id: json['id'],
      type: json['type'],
      attributes: PollAttributes.fromJson(json['attributes']),
    );
  }
}

class PollAttributes {
  final String question;
  final List<String> options;
  final int voteCount;
  final String endsAt;
  final bool isExpired;

  PollAttributes({
    required this.question,
    required this.options,
    required this.voteCount,
    required this.endsAt,
    required this.isExpired,
  });

  factory PollAttributes.fromJson(Map<String, dynamic> json) {
    return PollAttributes(
      question: json['question'],
      options: List<String>.from(json['options']),
      voteCount: json['vote_count'],
      endsAt: json['ends_at'],
      isExpired: json['is_expired'],
    );
  }
}

第二步:编写统一的数据获取和关联方法

我们只需要发起一次网络请求,然后把included里的实体存入一个Map(用type:id作为key,方便快速查找),再遍历data列表,把关联的实体填充到帖子对象中:

Future<List<Datum>> getPostsWithAssociatedData() async {
  try {
    Response response = await _dio.get(url);
    final jsonData = response.data;

    // 1. 处理included列表,存入Map以便快速查找
    final Map<String, dynamic> includedMap = {};
    if (jsonData['included'] != null) {
      for (final item in jsonData['included']) {
        final key = "${item['type']}:${item['id']}";
        if (item['type'] == 'user') {
          includedMap[key] = IncludedUser.fromJson(item);
        } else if (item['type'] == 'poll') {
          includedMap[key] = IncludedPoll.fromJson(item);
        }
        // 如果还有其他类型的实体,继续添加else if分支
      }
    }

    // 2. 处理data列表,并关联included中的实体
    final List<Datum> posts = [];
    for (final map in jsonData['data']) {
      final post = Datum.fromJson(map);

      // 关联帖子作者
      if (post.relationships.user != null) {
        final userKey = "user:${post.relationships.user!.id}";
        post.postUser = includedMap[userKey] as IncludedUser?;
      }

      // 关联帖子的投票
      if (post.relationships.poll != null) {
        final pollKey = "poll:${post.relationships.poll!.id}";
        post.postPoll = includedMap[pollKey] as IncludedPoll?;
      }

      // 关联被标记的用户列表
      final taggedUsers = <IncludedUser>[];
      for (final taggedUser in post.relationships.taggedUsers) {
        final userKey = "user:${taggedUser.id}";
        final user = includedMap[userKey] as IncludedUser?;
        if (user != null) {
          taggedUsers.add(user);
        }
      }
      post.taggedUsersList = taggedUsers;

      posts.add(post);
    }

    return posts;
  } catch (e) {
    print("反序列化出错: $e");
    return [];
  }
}

第三步:使用数据

调用这个方法后,你拿到的每个Datum对象都已经包含了关联的用户、投票等详情,直接使用即可:

// 示例调用
getPostsWithAssociatedData().then((posts) {
  for (final post in posts) {
    print("帖子作者: ${post.postUser?.attributes.displayName}");
    print("投票问题: ${post.postPoll?.attributes.question}");
    print("被标记的用户: ${post.taggedUsersList?.map((u) => u.attributes.displayName).join(', ')}");
  }
});

一些优化建议

  • 可以使用json_serializable包自动生成模型类的序列化/反序列化代码,减少手动编写的工作量和出错概率
  • 对于可能为null的字段,建议使用空安全的处理方式,避免运行时异常
  • 如果included里有更多类型的实体,只需要扩展includedMap的处理逻辑即可

备注:内容来源于stack exchange,提问作者who-aditya-nawandar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 13:14:12