at-import / Sassy-Maps

Map helper functions for Sass 3.3 and up
MIT License
66 stars 22 forks source link

Request: Deep Merge #9

Open Undistraction opened 10 years ago

Undistraction commented 10 years ago

There is not going to be a recursive/deep-merge added to Sass core anytime soon, so I would like to suggest you add it to Sassy-maps.

Example use: I keep all my colour-refs inside a nested map structure. I would like to merge project-specific colour-values into defaults, overriding anything that is already present.

Snugug commented 10 years ago

I'd recommend using Toolkit's Settings functionality to do what you're requesting. Otherwise, map-set-deep exists and should allow for that.

Undistraction commented 9 years ago

@Snugug I think it would be a really useful bit of functionality in Sassy Maps and would nicely complement map-get-deep and map-set-deep. In case you change your mind, here is a function that deep merges two maps:

@function map-merge-deep($map-old, $map-new, $overwrite: true){
  // Iterate through each value of the new map
  @each $key, $new-value in $map-new {
    // Check if that value already exists on the old map
    @if map-has-key($map-old, $key) {
      // There is an existing key
      $old-value: map-get($map-old, $key);
      @if type-of($new-value) == map and type-of($old-value) == map {
        // If both are maps, recurse regardless of $overwrite
        $merged-value: map-merge-deep($old-value, $new-value);
        $map-old: map-set($map-old, $key, $merged-value);
      }@else{
        // Otherwise check $overwrite
        @if $overwrite{
          $map-old: map-set($map-old, $key, $new-value);
        }
      }
    }@else{
      // There is no existing key so add
      $map-old: map-set($map-old, $key, $new-value);  
    }
  }
  @return $map-old
}

Sassmeister Gist here

Snugug commented 9 years ago

I guess I'm not sure what you're looking for exactly. What's the difference in output between set-deep and merge-deep? Also, if you'd like code to be considered, as a PR please.

Undistraction commented 9 years ago

I'm looking for a way to take two maps and combine them into a new map that contains all the values from the first map and all all the values from the second, overwriting the values from the first if they are already present.

The problem with map-merge is that it won't recurse into values that are themselves maps. The problem with map-set-deep is that I need to know the path into the map to change the value; to change all values I need to recurse through the map and call map-set-deep for each value to avoid blowing away existing values. The function above recurses through the map as deep as is needed and either creates or replaces values, ultimately returning the merged map.

If you look at the Sassmeister you will see the output is a merging of map-1 and map-2.

I would be more than happy to submit a pull request if this functionality is something you would find useful.

Here are two maps and the resulting merged map

$map-1:(
  alpha: 1,
  beta: 1,
  charlie:(
    delta: 1,
    echo: 1,
    foxtrot:(
      gamma: 1,
      hotel: 1
    )
  )
);

$map-2:(
  alpha: 2,
  charlie: (
    echo: 2,
    foxtrot: (
      hotel: 2,
      yankee:(
        omega: 2
      )
    )
  ),
  indigo: (
    juliette: 2
  )
);

$result:(
  alpha: 2, 
  beta: 1,
  charlie: (
    delta: 1,
    echo: 2,
    foxtrot: (
      gamma: 1,
      hotel: 2,
      yankee: (
        omega: 2
      ), 
      indigo: (
        juliette: 2
      )
    )
  )
cibulka commented 9 years ago

+1!!

Undistraction commented 9 years ago

@cibulka There is a PR open with the function or you can grab it here

cibulka commented 9 years ago

Thanks for an awesomely quick response! And about my question I've just deleted - everything works, I've just, well, forgot to include sassy-maps to my configuration. :) Thanks again, this helps me a lot!

Undistraction commented 9 years ago

@cibulka No problem. Glad it's helpful.

Undistraction commented 8 years ago

Gentle bump.

sinisterstumble commented 8 years ago

The following works for me

@function spire-map-extend($map, $maps.../*, $deep */) {
  $last: nth($maps, -1);
  $deep: $last == true;
  $max: if($deep, length($maps) - 1, length($maps));

  // Loop through all maps in $maps...
  @for $i from 1 through $max {
    // Store current map
    $current: nth($maps, $i);

    // If not in deep mode, simply merge current map with map
    @if not $deep {
      $map: map-merge($map, $current);
    } @else {
      // If in deep mode, loop through all tuples in current map
      @each $key, $value in $current {

        // If value is a nested map and same key from map is a nested map as well
        @if type-of($value) == "map" and type-of(map-get($map, $key)) == "map" {
          // Recursive extend
          $value: spire-map-extend(map-get($map, $key), $value, true);
        }

        // Merge current tuple with map
        $map: map-merge($map, ($key: $value));
      }
    }
  }

  @return $map;
}