@@ -329,6 +329,11 @@ final class Projection<T> {
329329 }
330330
331331 /// Moves the value from [from] and writes it to [to] .
332+ ///
333+ /// To merge all values from a map to another map, specify [from] as
334+ /// `from.*` and [to] as the target path.
335+ ///
336+ /// To move to the root map, specify [to] as `""` or `"."` .
332337 Projection <T > move (String from, String to) {
333338 transformers.add (Projections .move (from, to));
334339 return this ;
@@ -384,6 +389,15 @@ final class Projection<T> {
384389 }
385390 return engine.fromNative <T >(result, type: type, tree: tree);
386391 }
392+
393+ /// Applies the projection to the given optional [initial] map and returns the result as a map.
394+ Map <String , dynamic > performMap ([Map <String , dynamic >? initial]) {
395+ var result = initial ?? < String , dynamic > {};
396+ for (final transformer in transformers) {
397+ result = transformer (result);
398+ }
399+ return result;
400+ }
387401}
388402
389403/// A result of traversing a map.
@@ -400,6 +414,7 @@ class Projections {
400414 final subPaths = path.split ("." );
401415 dynamic value = map;
402416 for (var path in subPaths) {
417+ if (path.isEmpty) continue ;
403418 if (value is ! Map ) return (exists: false , value: null );
404419 if (! value.containsKey (path)) return (exists: false , value: null );
405420 value = value[path];
@@ -414,6 +429,7 @@ class Projections {
414429 final subPaths = path.split ("." );
415430 final result = < String , dynamic > {};
416431 var current = result;
432+ final isRoot = path.replaceAll ("." , "" ).isEmpty;
417433 for (var i = 0 ; i < subPaths.length - 1 ; i++ ) {
418434 final path = subPaths[i];
419435 if (current.containsKey (path)) {
@@ -423,27 +439,65 @@ class Projections {
423439 current = current[path];
424440 }
425441 }
442+ if (isRoot) return value;
426443 current[subPaths.last] = value;
427444 return $clone (map)..addAll (result);
428445 }
429446
447+ static Map <String ,dynamic > $move (Map <String , dynamic > map, String from, String to, bool delete) {
448+ final isWildcard = from.endsWith (".*" );
449+ if (isWildcard) {
450+ final path = from.substring (0 , from.length - 2 );
451+ final value = $get (map, path);
452+ if (! value.exists) return map;
453+ if (value.value is ! Map ) {
454+ throw ArgumentError ("Source path '$from ' is not a map" );
455+ }
456+ if (delete) {
457+ map = $delete (map, path);
458+ }
459+ final target = $get (map, to);
460+ if (! target.exists) {
461+ return $set (map, to, value.value);
462+ }
463+ if (target.value is ! Map ) {
464+ throw ArgumentError ("Target path '$to ' is not a map" );
465+ }
466+
467+ final targetMap = $clone ((target.value as Map <String , dynamic >? ) ?? < String , dynamic > {});
468+ targetMap.addAll (value.value as Map <String , dynamic >);
469+ return $set (map, to, targetMap);
470+ } else {
471+ final value = $get (map, from);
472+ if (! value.exists) return map;
473+ if (delete) {
474+ map = $delete (map, from);
475+ }
476+ return $set (map, to, value.value);
477+ }
478+ }
479+
430480 /// Deletes the value at [path] in the given [map] . Returns a new map
431481 /// with the updated values and leaves the original map untouched.
432482 static Map <String , dynamic > $delete (Map <String , dynamic > map, String path) {
483+ if (path.replaceAll ("." , "" ).isEmpty) return < String , dynamic > {};
433484 final subPaths = path.split ("." );
434- final result = < String , dynamic > {};
435- var current = result;
436- for (var i = 0 ; i < subPaths.length - 1 ; i++ ) {
437- final path = subPaths[i];
438- if (current.containsKey (path)) {
439- current = current[path];
485+
486+ final Map <String , dynamic > result = $clone (map);
487+ Map <String , dynamic > current = result;
488+
489+ for (int i = 0 ; i < subPaths.length - 1 ; i++ ) {
490+ final key = subPaths[i];
491+ if (current[key] is Map <String , dynamic >) {
492+ current[key] = Map <String , dynamic >.from (current[key]);
493+ current = current[key];
440494 } else {
441- current[path] = < String , dynamic > {};
442- current = current[path];
495+ return result;
443496 }
444497 }
498+
445499 current.remove (subPaths.last);
446- return $clone (map).. addAll ( result) ;
500+ return result;
447501 }
448502
449503 /// Deep clones the given [map] and returns a new map with the same values.
@@ -498,12 +552,7 @@ class Projections {
498552
499553 /// Applies a transformer that moves the value at [from] to [to] .
500554 static ProjectionTransformer move (String from, String to) {
501- return (data) {
502- final result = $get (data, from);
503- if (! result.exists) return data;
504- data = $delete (data, from);
505- return $set (data, to, result.value);
506- };
555+ return (data) => Projections .$move (data, from, to, true );
507556 }
508557}
509558
0 commit comments