首页 > 解决方案 > Dart Flutter 中的嵌套 groupby

问题描述

我正在尝试在 Flutter/Dart 中开发一个应用程序,但我一直坚持这一点。

问题

我从 db 获取这些数据:

[
   {
      "id":1,
      "tId":1,
      "langId":1,
      "langName":"English",
      "title":"Title A",
      "bookmark":0,
      "content":[
         {
            "sId":"1a",
            "scontent":"Content A"
         },
         {
            "sId":"1b",
            "scontent":"Content A1"
         },
         {
            "sId":"1c",
            "scontent":"Content A2"
         }
      ]
   },
   {
      "id":2,
      "tId":1,
      "langId":2,
      "langName":"French",
      "title":"Title (in French) A",
      "bookmark":0,
      "content":[
         {
            "sId":"1a",
            "scontent":"Content French A"
         },
         {
            "sId":"1b",
            "scontent":"Content French A1"
         },
         {
            "sId":"1c",
            "scontent":"Content French A2"
         }
      ]
   },
   {
      "id":3,
      "tId":1,
      "langId":3,
      "langName":"German",
      "title":"Title (in German) A",
      "bookmark":0,
      "content":[
         {
            "sId":"1a",
            "scontent":"Content German A"
         },
         {
            "sId":"1b",
            "scontent":"Content German A1"
         },
         {
            "sId":"1c",
            "scontent":"Content German A2"
         }
      ]
   },
   {
      "id":4,
      "tId":2,
      "langId":1,
      "langName":"English",
      "title":"Title B",
      "bookmark":0,
      "content":[
         {
            "sId":"2a",
            "scontent":"Content B"
         },
         {
            "sId":"2b",
            "scontent":"Content B1"
         },
         {
            "sId":"2c",
            "scontent":"Content B2"
         }
      ]
   },
   {
      "id":5,
      "tId":2,
      "langId":2,
      "langName":"French",
      "title":"Title (in French) B",
      "bookmark":0,
      "content":[
         {
            "sId":"2a",
            "scontent":"Content French B"
         },
         {
            "sId":"2b",
            "scontent":"Content French B1"
         },
         {
            "sId":"2c",
            "scontent":"Content French B2"
         }
      ]
   },
   {
      "id":6,
      "tId":2,
      "langId":3,
      "langName":"German",
      "title":"Title (in German) B",
      "bookmark":0,
      "content":[
         {
            "sId":"2a",
            "scontent":"Content German B"
         },
         {
            "sId":"2b",
            "scontent":"Content German B1"
         },
         {
            "sId":"2c",
            "scontent":"Content German B2"
         }
      ]
   }
]

我正在映射到这个 PODO:

class ABC {

  int? id;
  int? tId;
  int? langID;
  String? langName;
  String? title;
  List<Content>? content;
  int isBookmark = 0; //0 False | 1 True

  ABC(this.id, this.tId, this.langID, this.langName, this.title, this.content, this.isBookmark);

}

class Content {

 int? sId;
 String? content;

 Content(this.sId, this.content);
 
}

我想按上述数据进行分组,tId对于不同语言的每个内容都使用相同的数据。我可以通过使用成功地做到这一点groupBy

List<ABC>? data = snapshot.data;
var groupByTId = groupBy(
              data, (obj) => (obj as ABC).tId).values.toList();

数据如下所示:

[
   {
      "0":[
         {
            "0":[
               {
                  "id":1,
                  "tId":1,
                  "langId":1,
                  "langName":"English",
                  "title":"Title A",
                  "bookmark":0,
                  "content":[
                     {
                        "sId":"1a",
                        "scontent":"Content A"
                     },
                     {
                        "sId":"1b",
                        "scontent":"Content A1"
                     },
                     {
                        "sId":"1c",
                        "scontent":"Content A2"
                     }
                  ]
               }
            ],
            "1":[
               {
                  "id":2,
                  "tId":1,
                  "langId":2,
                  "langName":"French",
                  "title":"Title (in French) A",
                  "bookmark":0,
                  "content":[
                     {
                        "sId":"1a",
                        "scontent":"Content French A"
                     },
                     {
                        "sId":"1b",
                        "scontent":"Content French A1"
                     },
                     {
                        "sId":"1c",
                        "scontent":"Content French A2"
                     }
                  ]
               }
            ],
            "2":[
               {
                  "id":3,
                  "tId":1,
                  "langId":3,
                  "langName":"German",
                  "title":"Title (in German) A",
                  "bookmark":0,
                  "content":[
                     {
                        "sId":"1a",
                        "scontent":"Content German A"
                     },
                     {
                        "sId":"1b",
                        "scontent":"Content German A1"
                     },
                     {
                        "sId":"1c",
                        "scontent":"Content German A2"
                     }
                  ]
               }
            ]
         }
      ],
      "1":[
         {
            "0":[
               {
                  "id":4,
                  "tId":2,
                  "langId":1,
                  "langName":"English",
                  "title":"Title B",
                  "bookmark":0,
                  "content":[
                     {
                        "sId":"2a",
                        "scontent":"Content B"
                     },
                     {
                        "sId":"2b",
                        "scontent":"Content B1"
                     },
                     {
                        "sId":"2c",
                        "scontent":"Content B2"
                     }
                  ]
               }
            ],
            "1":[
               {
                  "id":5,
                  "tId":2,
                  "langId":2,
                  "langName":"French",
                  "title":"Title (in French) B",
                  "bookmark":0,
                  "content":[
                     {
                        "sId":"2a",
                        "scontent":"Content French B"
                     },
                     {
                        "sId":"2b",
                        "scontent":"Content French B1"
                     },
                     {
                        "sId":"2c",
                        "scontent":"Content French B2"
                     }
                  ]
               }
            ],
            "2":[
               {
                  "id":6,
                  "tId":2,
                  "langId":3,
                  "langName":"German",
                  "title":"Title (in German) B",
                  "bookmark":0,
                  "content":[
                     {
                        "sId":"2a",
                        "scontent":"Content German B"
                     },
                     {
                        "sId":"2b",
                        "scontent":"Content German B1"
                     },
                     {
                        "sId":"2c",
                        "scontent":"Content German B2"
                     }
                  ]
               }
            ]
         }
      ]
   }
]

现在,我想进一步groupby使用sId. 我想sId在一个对象数组中合并相同的值。但我不确定如何进行。我试过循环遍历 ABC 然后 groupingBysId但它没有将所有sIds 合并到一个数组中。

var groupByTId = groupBy(
              data, (obj) => (obj as ABC).tId).values.toList();
          
          for (var objects in groupByTId){
              for (var content in objects){
                var newList = groupBy(content.content!, (Content oj) => oj.sId).values.toList(); <-- doesn't group the data based on sId.
              }
          }

基本上,我希望数据采用这种格式:

[{
        "1": [{
            "title": "Title A",
            "bookmark": 0,
            "content": [{
                "0": [{
                        "sId": "1a", <-- all 1a's should be together.
                        "scontent": "Content A",

                    },
                    {
                        "sId": "1a",
                        "scontent": "Content French A",

                    },
                    {
                        "sId": "1a",
                        "scontent": "Content German A",

                    }
                ],
                "1": [{
                        "sId": "1b",
                        "scontent": "Content A1",

                    },
                    {
                        "sId": "1b",
                        "scontent": "Content French A1",

                    },
                    {
                        "sId": "1b",
                        "scontent": "Content German A1",

                    }
                ],
                "2": [{
                        "sId": "1c",
                        "scontent": "Content A2",

                    },
                    {
                        "sId": "1c",
                        "scontent": "Content French A2",

                    },
                    {
                        "sId": "1c",
                        "scontent": "Content German A2",

                    }
                ]
            }]
        }],
    "2": [{
            "title": "Title B",
            "bookmark": 0,
            "content": [{
                "0": [{
                        "sId": "2a",
                        "scontent": "Content B",

                    },
                    {
                        "sId": "2a",
                        "scontent": "Content French B",

                    },
                    {
                        "sId": "2a",
                        "scontent": "Content German B",

                    }
                ],
                "1": [{
                        "sId": "2b",
                        "scontent": "Content B1",

                    },
                    {
                        "sId": "2b",
                        "scontent": "Content French B1",
                    },
                    {
                        "sId": "2b",
                        "scontent": "Content German B1",

                    }
                ],
                "2": [{
                        "sId": "2c",
                        "scontent": "Content B2",

                    },
                    {
                        "sId": "2c",
                        "scontent": "Content French B2",

                    },
                    {
                        "sId": "2c",
                        "scontent": "Content German B2",

                    }
                ]
            }]
        }]
}]

非常感谢您的帮助。

标签: flutterdart

解决方案


该问题要求对内部内容执行嵌套组,但实际请求是将外部组压缩为单个对象,该对象聚合ABC共享tId值的所有对象的信息。title更重要的是,结果数据的示例有些随意地为and选择了一个值,并且完全bookmark丢弃了id,和。因此,您提出的请求总是会导致解决方案包含一定程度的猜测和任意性。langIdlangName

另一个问题是,由于您ABC在第二次“分组”之后从根本上改变了对象的结构,因此您发布的 POGO 将不再适用于生成的结构。然后,该解决方案将需要生成一个全新的 POGO 以适应新结构。

因此,这是一个潜在的解决方案。请注意,由于groupBy它不是标准 Dart 库的一部分,并且您没有指定要使用的包,因此我将使用我自己的darq库来实现此功能。


主要逻辑:

import 'package:darq/darq.dart';

void main() {
  // Deserialize data into POGOs
  final abcs = data.map((d) => ABC.fromMap(d)).toList();

  // Group by tId
  final grouped = abcs.groupBy((d) => d.tId); 
  
  // Aggregate the previous groups
  final collapsed = grouped.map(
    (g) => g.fold(<int, ABCPacked>{}, (Map<int, ABCPacked> map, ABC abc) {
      // Create the aggregate packed ABC object that subsequent
      // ABCs will dump their data into.
      if (!map.containsKey(abc.tId)) {
        map[abc.tId] = ABCPacked(
          abc.tId,
          abc.title,
          abc.isBookmark,
        );
      }
      final packedContent = map[abc.tId]!;
      // Loop through the content of the ABC object and sort
      // it into the resulting packed ABC based on sId
      abc.content.forEach((Content content) {
        if (!packedContent.content.containsKey(content.sId)) {
          packedContent.content[content.sId] = <Content>[];
        }
        packedContent.content[content.sId]?.add(content);
      });
      return map;
    }),
  );
}

传入数据 POGO:

class ABC {
  final int id;
  final int tId;
  final int langID;
  final String langName;
  final String title;
  final bool isBookmark; // The database may treat this like an int, but there's no reason to keep it as one
  final List<Content> content;

  ABC(this.id, this.tId, this.langID, this.langName, this.title, this.isBookmark, this.content);

  factory ABC.fromMap(Map<String, dynamic> map) {
    assert(map.containsKey('id') && map['id']! is int);
    assert(map.containsKey('tId') && map['tId']! is int);
    assert(map.containsKey('langId') && map['langId']! is int);
    assert(map.containsKey('langName') && map['langName']! is String);
    assert(map.containsKey('title') && map['title']! is String);
    assert(map.containsKey('bookmark') && map['bookmark']! is int);
    assert(map.containsKey('content') && map['content']! is List);

    final content = (map['content']! as List<dynamic>).map((c) => Content.fromMap(c));
    return ABC(
      map['id']!,
      map['tId']!,
      map['langId']!,
      map['langName']!,
      map['title']!,
      map['bookmark']! == 1,
      content.toList(),
    );
  }

  @override
  String toString() {
    return 'ABC {id: $id, tId: $tId, langID: $langID, langName: $langName, title: $title, isBookmark: $isBookmark, content: $content}';
  }
}

class Content {
  final String sId; // int doesn't match the type from the source data
  final String content;

  Content(this.sId, this.content);
 
  factory Content.fromMap(Map<String, dynamic> map) {
    assert(map.containsKey('sId') && map['sId']! is String);
    assert(map.containsKey('scontent') && map['scontent']! is String);

    return Content(map['sId'], map['scontent']);
  }

  @override
  String toString() {
    return 'Content {sId: $sId, content: $content}';
  }
}

新的结果数据 POGO:

class ABCPacked {
  final int tId;
  final String title;
  final bool isBookmark;
  final Map<String, List<Content>> content = {};

  ABCPacked(this.tId, this.title, this.isBookmark);

  @override
  String toString() {
    return 'ABCPacked {tId: $tId, title: $title, isBookmark: $isBookmark, content: $content}';
  }
}

结果数据结构:

{
  1: ABCPacked {
    tId: 1, 
    title: Title A, 
    isBookmark: false, 
    content: {
      1a: [
        Content {sId: 1a, content: Content A}, 
        Content {sId: 1a, content: Content French A}, 
        Content {sId: 1a, content: Content German A}
      ], 
      1b: [
        Content {sId: 1b, content: Content A1}, 
        Content {sId: 1b, content: Content French A1}, 
        Content {sId: 1b, content: Content German A1}
      ], 1c: [
        Content {sId: 1c, content: Content A2}, 
        Content {sId: 1c, content: Content French A2}, 
        Content {sId: 1c, content: Content German A2}
      ]
    }
  }
},
{
  2: ABCPacked {
    tId: 2, 
    title: Title B, 
    isBookmark: false, 
    content: {
      2a: [
        Content {sId: 2a, content: Content B}, 
        Content {sId: 2a, content: Content French B}, 
        Content {sId: 2a, content: Content German B}
      ], 
      2b: [
        Content {sId: 2b, content: Content B1}, 
        Content {sId: 2b, content: Content French B1}, 
        Content {sId: 2b, content: Content German B1}
      ], 
      2c: [
        Content {sId: 2c, content: Content B2}, 
        Content {sId: 2c, content: Content French B2}, 
        Content {sId: 2c, content: Content German B2}
      ]
    }
  }
}

请注意,这种方法为给定ABCtitleisBookmark值选择使用哪个值的方式是只使用它在创建结果时找到的第一个值ABCPacked。这种方法假定您从数据库中获取的值与您想要的值在顶部是有序的,我不能保证会是这种情况。如果您希望使用一些替代逻辑来选择这些值,则需要相应地更改此解决方案。


推荐阅读