Last active
June 19, 2019 18:25
-
-
Save dfkaye/bb240ae958e6f21c021f8f3b031f0eff to your computer and use it in GitHub Desktop.
update() sets values at key paths in data, returns new data
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
// 29 May 2019 | |
// another deep setter function but that first clones the target object before | |
// setting a new value at the key path - essentially an 'immutable' version of | |
// _.set @see https://gist.github.com/dfkaye/1b277589b04753cf0211eca25ea25b1d | |
// companion to resolve() function | |
// @see https://gist.github.com/dfkaye/5828ef43e2dccea576c78bb4eeb9747e | |
// Note: handles a single key path rather than a batch of updates making it | |
// potentially expensive (clone explosion). | |
// 19 June 2019 - support injection/creation of array or objects if not-last | |
// key is missing. | |
/** | |
* @function update takes structured params, data, path, and value, and tries | |
* to set the value at the path in the data. A clone of the data is returned, | |
* with or without mutated values. | |
* | |
* TODO - if necessary, shaving empty string keys from a data structure should | |
* be handled by another function... | |
* | |
* @param {object} data | |
* @param {string} path, dot.delimited.path in the data structure | |
* @param {*} value, the new value to be assigned at the path in the data | |
* @returns {object} clone of data | |
*/ | |
// export | |
function update({ data, path, value }) { | |
let entity = Object.assign({}, data); | |
let target = entity; | |
let keys = path.split('.'); | |
let last = keys.length - 1; | |
/* | |
* Use [].every to halt processing when entity can't be assigned a value. | |
* If every key is found, target should be assignable. | |
* Only assign to the last target in the key path. | |
*/ | |
const assignable = path.split('.').every(function(key, position) { | |
// Only step down the target path if we're not at last position. | |
if (position < last) { | |
if (!(key in target)) { | |
/* | |
* Inject an array or object at key if not present in target. | |
* If next key is digit, create an array, else create an object. | |
*/ | |
const next = keys[position + 1]; | |
target[key] = /^[\d]+$/.test(next) ? [] : {}; | |
} | |
// Step down. | |
target = target[key]; | |
} | |
/* | |
* Processing continues if the following is true: | |
* entity is assignable if it is an object, not null or undefined. | |
*/ | |
return (typeof target === 'object') && target != null; | |
}); | |
assignable && (target[keys[last]] = value); | |
return entity; | |
} | |
/* test it out */ | |
var tests = [ | |
{ data: { key: 'should update' }, path: 'key', value: 'updated' }, | |
{ data: { key: 'should not update' }, path: 'wrong.key', value: 'updated' }, | |
{ data: { key: 'should update' }, path: 'key', value: { name: 'updated' } }, | |
{ data: { path: { to: { name: 'should update' }}}, path: 'path.to.name', value: 'updated' }, | |
{ data: { path: { to: { name: 'should not update' }}}, path: 'path.to.wrong.name', value: 'updated' }, | |
{ data: { path: { to: { name: 'should update' }}}, path: 'path.to.name', value: { first: 'updated', last: 'name' } }, | |
{ data: { key: { /* should inject*/ } }, path: 'key.to.name', value: 'aloha' }, | |
{ data: { key: { /* should inject*/ } }, path: 'key.to.array.1', value: 'aloha' } | |
]; | |
var results = tests.map((test) => { | |
var result = update({ data: test.data, path: test.path, value: test.value }); | |
return result.key || result.path.to.name; | |
}); | |
console.log( | |
JSON.stringify(results, null, 2) | |
); | |
/* | |
[ | |
"updated", | |
"should not update", | |
{ | |
"name": "updated" | |
}, | |
"updated", | |
"should not update", | |
{ | |
"first": "updated", | |
"last": "name" | |
}, | |
{ | |
"to": { | |
"name": "aloha" | |
} | |
}, | |
{ | |
"to": { | |
"array": [ | |
null, | |
"aloha" | |
] | |
} | |
} | |
] | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment