Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active June 19, 2019 18:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/bb240ae958e6f21c021f8f3b031f0eff to your computer and use it in GitHub Desktop.
Save dfkaye/bb240ae958e6f21c021f8f3b031f0eff to your computer and use it in GitHub Desktop.
update() sets values at key paths in data, returns new data
// 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