lukeed / dset

A tiny (194B) utility for safely writing deep Object values~!
MIT License
754 stars 22 forks source link

Consider adding array merge strategy option #31

Open webketje opened 2 years ago

webketje commented 2 years ago

Given following code:

const host = { arr: ['hello'] }
const { dset } = require('dset/merge')
dset(host, 'arr', ['world'])

// or
const host = { arr: ['hello'] }
const { merge } = require('dset/merge')
merge({arr: ['hello']}, {arr: ['world']})

I would expect the result to be { arr: ['hello', 'world'] }, however currently the result is { arr: ['world'] } If I change the second arr to barr the result is { arr: ['hello'], barr: ['world'] }

The README says:

The main/default dset module forcibly writes values at the assigned key-path. However, in some cases, you may prefer to merge values at the key-path

So I would expect arrays to merge too instead of being forcibly overwritten as in the 'standard' dset Anyhow i understand the merge logic is similar to lodash.merge.
The deepmerge module has a merge strategy option, but it only handles merging, not what dset does.
It would be great if dset could be used with a strategy of "add" instead of "overwrite".

+ strat = strat || (a, b, k) => { a[k] = merge(a[k], b[k]) };
if (Array.isArray(a) && Array.isArray(b)) {
  for (k=0; k < b.length; k++) {
-    a[k] = merge(a[k], b[k]);
+   strat(a, b, k)
  }
}
+ // alt strat: (a, b, k) => a.push(b[k])
paulovieira commented 2 years ago

This was also my expectation about dset/merge when dealing with arrays. But thinking a bit about this, it's not so clear what is meant by "merging" in the context of arrays. Usually the new elements should be placed at the end, but sometimes might be at the beginning.

Anyway, it's easy to create a wrapper around dset/merge to handle this case:

import delve from 'dlv';
import { dset as dsetMergeOriginal } from 'dset/merge';

function dset(obj, keyPath, val, arrayMethod = 'push') {
  let nestedValue = delve(obj, keyPath);

  // manually handle merging of arrays ('dset/merge' will only merge objects)

  if (Array.isArray(nestedValue) && Array.isArray(val)) {
    for (let i = 0; i < val.length; i++) {
      nestedValue[arrayMethod](val[i]);
    }
  }
  else {
    dsetMergeOriginal(obj, keyPath, val); 
  }
}

export {
  dset
}