unflattenJson method

Map<String, dynamic> unflattenJson(
  1. Map<String, dynamic> input, {
  2. String separator = '.',
})

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;
}