flatten function Null safety
flatten is similar to pandas' json_normalize function, this function recursively flattens the nested JSON objects into a flat tabular list. It returns List<Map<String, dynamic>> of the flattened records.
Simple example:
[
{"id": 1, "name": {"first": "Coleen", "last": "Volk"}},
{"name": {"given": "Mark", "family": "Regner"}},
{"id": 2, "name": "Faye Raker"},
]
flatten(input);
Results in:
[
{"id": 1, "name.first": "Coleen", "name.last": "Volk", "name.given": null, "name.family": null},
{"id": null, "name.first": null, "name.last": null, "name.given": "Mark", "name.family": "Regner"},
{"id": 2, "name": "Faye Raker", "name.first": null, "name.last": null, "name.given": null, "name.family": null},
]
Advanced example:
[
{
"state": "Florida",
"shortname": "FL",
"info": {"governor": "Rick Scott"},
"counties": [
{"name": "Dade", "population": 12345},
{"name": "Broward", "population": 40000},
{"name": "Palm Beach", "population": 60000},
],
},
{
"state": "Ohio",
"shortname": "OH",
"info": {"governor": "John Kasich"},
"counties": [
{"name": "Summit", "population": 1234},
{"name": "Cuyahoga", "population": 1337},
],
},
]
flatten(input, recordPath: ["counties"], includePath: [["state"], ["shortname"], ["info", "governor"]]);
Results in:
[
{
"state": "Florida",
"shortname": "FL",
"info.governor": "Rick Scott",
"name": "Dade",
"population": 12345
},
{
"state": "Florida",
"shortname": "FL",
"info.governor": "Rick Scott",
"name": "Broward",
"population": 40000
},
{
"state": "Florida",
"shortname": "FL",
"info.governor": "Rick Scott",
"name": "Palm Beach",
"population": 60000
},
{
"state": "Ohio",
"shortname": "OH",
"info.governor": "John Kasich",
"name": "Summit",
"population": 1234
},
{
"state": "Ohio",
"shortname": "OH",
"info.governor": "John Kasich",
"name": "Cuyahoga",
"population": 1337
},
]
Implementation
List<Map<String, dynamic>> flatten(
/// Json to flatten
dynamic json, {
/// Separator used to join the path of the nested keys
String separator = '.',
/// Json path of records in the given json. This will be the entry point for the flattening.
List<String> recordPath = const [],
/// Json path from the root of data to extract from the nested records.
/// Each path will become a column in the resulting table.
List<List<String>> includePath = const [],
/// Add missing fields or column to the records.
bool addMissingKeys = true,
// TODO: Implement maxLevel
/// Level of nesting to flatten
int maxLevel = -1,
}) {
final result = <Map<String, dynamic>>[];
final seenKeys = addMissingKeys ? <String>{} : null;
final includeResult = <String, dynamic>{};
final includeKeys = includePath.map((i) => i.join(separator)).toSet();
final commonPath = includePath.isNotEmpty
? (includePath
.map((i) => i
.headIntersect(recordPath)
// .whereNot((e) => e == '*')
.join(separator))
.toSet())
: const <String>{};
// if (kDebugMode) print(includeKeys);
// if (kDebugMode) print(commonPath);
_flatten(
json,
separator,
const [],
recordPath,
includeKeys,
commonPath,
maxLevel,
listAccumulator(result, seenKeys),
includeAccumulator(includeResult, includeKeys),
);
// if (kDebugMode) print(includeResult);
if (seenKeys != null) {
for (final e in result) {
for (final k in seenKeys) {
e.putIfAbsent(k, () => null);
}
}
}
return result;
}