-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for unflatten function (#9)
* Add support for unflatten * chore: Move to separate files * chore: Configure env * feat: Basic test * fix: Mixing flattened maps * feat: Port flat.js tests * fix: Remove support for nested maps * Fixed unflatten function to work with nested objects in arrays, fixed some tests * fix: Throw ArgumentError if not flat --------- Co-authored-by: Danilo Fuchs <[email protected]>
- Loading branch information
1 parent
ff3ba1b
commit efbb316
Showing
10 changed files
with
534 additions
and
224 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"flutterSdkVersion": "3.13.8", | ||
"flavors": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,2 @@ | ||
/// Flatten a nested Map into a single level map | ||
/// | ||
/// If no [delimiter] is specified, will separate depth levels by `.`. | ||
/// | ||
/// If you don't want to flatten arrays (with 0, 1,... indexes), | ||
/// use [safe] mode. | ||
/// | ||
/// To avoid circular reference issues or huge calculations, | ||
/// you can specify the [maxDepth] the function will traverse. | ||
Map<String, dynamic> flatten( | ||
Map<String, dynamic> target, { | ||
String delimiter = ".", | ||
bool safe = false, | ||
int? maxDepth, | ||
}) { | ||
final result = <String, dynamic>{}; | ||
|
||
void step( | ||
Map<String, dynamic> obj, [ | ||
String? previousKey, | ||
int currentDepth = 1, | ||
]) { | ||
obj.forEach((key, value) { | ||
final newKey = previousKey != null ? "$previousKey$delimiter$key" : key; | ||
|
||
if (maxDepth != null && currentDepth >= maxDepth) { | ||
result[newKey] = value; | ||
return; | ||
} | ||
if (value is Map<String, dynamic>) { | ||
return step(value, newKey, currentDepth + 1); | ||
} | ||
if (value is List && !safe) { | ||
return step( | ||
_listToMap(value), | ||
newKey, | ||
currentDepth + 1, | ||
); | ||
} | ||
result[newKey] = value; | ||
}); | ||
} | ||
|
||
step(target); | ||
|
||
return result; | ||
} | ||
|
||
Map<String, T> _listToMap<T>(List<T> list) => | ||
list.asMap().map((key, value) => MapEntry(key.toString(), value)); | ||
export './src/flatten.dart'; | ||
export './src/unflatten.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/// Flatten a nested Map into a single level map | ||
/// | ||
/// If no [delimiter] is specified, will separate depth levels by `.`. | ||
/// | ||
/// If you don't want to flatten arrays (with 0, 1,... indexes), | ||
/// use [safe] mode. | ||
/// | ||
/// To avoid circular reference issues or huge calculations, | ||
/// you can specify the [maxDepth] the function will traverse. | ||
Map<String, dynamic> flatten( | ||
Map<String, dynamic> target, { | ||
String delimiter = ".", | ||
bool safe = false, | ||
int? maxDepth, | ||
}) { | ||
final result = <String, dynamic>{}; | ||
|
||
void step( | ||
Map<String, dynamic> obj, [ | ||
String? previousKey, | ||
int currentDepth = 1, | ||
]) { | ||
obj.forEach((key, value) { | ||
final newKey = previousKey != null ? "$previousKey$delimiter$key" : key; | ||
|
||
if (maxDepth != null && currentDepth >= maxDepth) { | ||
result[newKey] = value; | ||
return; | ||
} | ||
if (value is Map<String, dynamic>) { | ||
return step(value, newKey, currentDepth + 1); | ||
} | ||
if (value is List && !safe) { | ||
return step( | ||
_listToMap(value), | ||
newKey, | ||
currentDepth + 1, | ||
); | ||
} | ||
result[newKey] = value; | ||
}); | ||
} | ||
|
||
step(target); | ||
|
||
return result; | ||
} | ||
|
||
Map<String, T> _listToMap<T>(List<T> list) => | ||
list.asMap().map((key, value) => MapEntry(key.toString(), value)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/// Unflatten a map with keys such as `a.b.c: value` to a nested Map structure `{a: {b: {c: value}}}`. | ||
/// | ||
/// If no [delimiter] is specified, will separate depth levels by `.`. | ||
/// | ||
/// Unflattens arrays given that the keys are integers. | ||
/// | ||
/// Throws [ArgumentError] if any key is already a Map or List. | ||
/// | ||
/// Throws [ArgumentError] if there are conflicting keys. | ||
Map<String, dynamic> unflatten( | ||
Map<String, dynamic> flatMap, { | ||
String delimiter = ".", | ||
}) { | ||
final Map<String, dynamic> result = {}; | ||
|
||
flatMap.forEach((key, value) { | ||
if (value is Map) { | ||
throw ArgumentError('Expected flat map, but key "$key" is a Map'); | ||
} | ||
if (value is List) { | ||
throw ArgumentError('Expected flat map, but key "$key" is a List'); | ||
} | ||
}); | ||
|
||
flatMap.forEach((key, value) { | ||
final keys = key.split(delimiter); | ||
dynamic current = result; | ||
|
||
for (int i = 0; i < keys.length; i++) { | ||
final k = keys[i]; | ||
if (i == keys.length - 1) { | ||
// Last key, assign the value | ||
if (_isInteger(k)) { | ||
final int index = int.parse(k); | ||
while ((current as List).length <= index) { | ||
current.add(null); // Padding the array | ||
} | ||
current[index] = value; | ||
} else { | ||
if ((current as Map).containsKey(k)) { | ||
throw ArgumentError('Cannot unflatten, key "$k" already exists'); | ||
} | ||
current[k] = value; | ||
} | ||
} else { | ||
// Not the last key, we might need to create a map or array | ||
if (_isInteger(k)) { | ||
final int index = int.parse(k); | ||
while ((current as List).length <= index) { | ||
current.add(null); // Padding the array | ||
} | ||
// Ensure that we have a Map at the index if the next key is not an integer | ||
if (!_isInteger(keys[i + 1]) && | ||
(current[index] == null || current[index] is! Map)) { | ||
current[index] = {}; | ||
} | ||
current = current[index]; | ||
} else { | ||
if ((current as Map)[k] == null) { | ||
// Next key will tell us whether to create a list or a map | ||
current[k] = _isInteger(keys[i + 1]) ? [] : <String, dynamic>{}; | ||
} | ||
current = current[k]; | ||
} | ||
} | ||
} | ||
}); | ||
|
||
return result; | ||
} | ||
|
||
bool _isInteger(String? value) { | ||
if (value == null) { | ||
return false; | ||
} | ||
return int.tryParse(value) != null; | ||
} |
Oops, something went wrong.