unflattenJson method
Rebuilds nested maps from a flattened map, the inverse of flattenJson
for nested maps: {'a.b': 1, 'a.c': 2} becomes
{'a': {'b': 1, 'c': 2}}.
Each key is split on separator and its value placed at that path.
Lists are not rebuilt: flattenJson's list indices come back as map
keys ({'list.0': 10} becomes {'list': {'0': 10}}), because a map
whose keys happen to be 0, 1, … cannot be told apart from a list.
Throws StateError when one key is both a value and the parent of
another key ({'a': 1, 'a.b': 2}), since keeping either would silently
drop the other. Throws ArgumentError when separator is empty.
Implementation
Map<String, dynamic> unflattenJson(
Map<String, dynamic> input, {
String separator = '.',
}) {
if (separator.isEmpty) {
throw ArgumentError.value(separator, 'separator', 'Must not be empty');
}
final result = <String, dynamic>{};
// The maps this method created as parents. A leaf value that happens to
// be a Map (e.g. an empty map from flattenJson) is not one of them, so a
// key below it is still a collision.
final parents = Set<Map<String, dynamic>>.identity()..add(result);
for (final entry in input.entries) {
final segments = entry.key.split(separator);
var node = result;
for (var i = 0; i < segments.length - 1; i++) {
final segment = segments[i];
final existing = node[segment];
if (existing is Map<String, dynamic> && parents.contains(existing)) {
node = existing;
continue;
}
if (node.containsKey(segment)) {
throw StateError(
'[JsonUtility.unflattenJson] Key "${entry.key}" needs '
'"${segments.take(i + 1).join(separator)}" to be a parent, but '
'that key already holds a value.',
);
}
final child = <String, dynamic>{};
parents.add(child);
node[segment] = child;
node = child;
}
final leaf = segments.last;
if (node.containsKey(leaf)) {
throw StateError(
'[JsonUtility.unflattenJson] Key "${entry.key}" collides with '
'another key: "${entry.key}" is already the parent of other keys.',
);
}
node[leaf] = entry.value;
}
return result;
}