keypather v3.1.0
keypather
Get, set, or delete a deep value using a keypath string.
A collection of keypath utilities: get, set, delete, in, has, flatten, expand, and immutable set/delete.
Lightweight and parses keypaths using vanilla JS - No eval
or new Function
hacks!
Installation
npm install keypather
Usage
Examples
Import
// modular imports, so you can keep your bundle lean
const get = require('keypather/get')
const set = require('keypather/set')
const del = require('keypather/del')
const immutableSet = require('keypather/immutable-set')
const immutableDel = require('keypather/immutable-del')
const keypathIn = require('keypather/in')
const hasKeypath = require('keypather/has')
const expand = require('keypather/expand')
const flatten = require('keypather/flatten')
GET, SET, DEL Example
const get = require('keypather/get')
const set = require('keypather/set')
const del = require('keypather/del')
let obj
// Objects
obj = { foo: { bar: 100 } }
get(obj, 'foo.bar') // returns 100
del(obj, '["foo"]["bar"]') // returns true, obj becomes { foo: {} }
set(obj, 'foo.bar.qux', 200) // returns 200, obj becomes { foo: { bar: { qux: 200 } } }
get(obj, 'foo["bar"].qux') // returns 200
// Arrays
obj = {}
set(obj, 'foo[0]', 100) // obj is { foo: [ 100 ] }
Immutable SET, DEL Example
const set = require('keypather/immutable-set')
const del = require('keypather/immutable-del')
let obj
let out
// Objects
obj = { foo: { bar: 100 } }
out = set(obj, 'foo.bar', 100) // returns obj
// out === obj,
// since it was not modified
out = set(obj, 'foo.bar.qux', 200) // returns { foo: { bar: { qux: 200 } } }
// out !== obj,
// obj is still { foo: { bar: 100 } }
out = del(obj, 'one.two.three') // returns obj
// out === obj,
// since it was not modified
out = del(obj, 'foo.bar.qux') // returns { foo: { bar: {} } }
// out !== obj,
// obj is still { foo: { bar: { qux: 200 } } }
// Arrays
obj = {}
out = set(obj, 'foo[0]', 100) // returns { foo: [ 100 ] } (new)
// out !== obj, obj is still { foo: { bar: 100 } }
HAS, IN Example
const hasKeypath = require('keypather/has')
const keypathIn = require('keypather/in')
const obj = { foo: Object.create({ bar: 100 }) }
hasKeypath(obj, 'foo.bar') // returns false (bar is on proto)
keypathIn(obj, 'foo.bar') // returns true
hasKeypath(obj, 'foo') // returns true
FLATTEN, EXPAND Example
const expand = require('keypather/expand')
const flatten = require('keypather/flatten')
const obj = expand({
'foo.bar': 1,
'foo.qux[0]': 100,
'foo["qux"][1]': 200,
'foo.qux.wut': 'val'
})
// obj is { foo { bar: 1, qux: [ 100, 200, wut: 'val' ] } }
const flat = flatten(obj)
// flat is { 'foo.bar': 1, 'foo.qux': 2 } }
Errors Example
/* Missing deep values w/ "force: false" */
get({}, 'foo.bar', { force: false })
set({}, 'foo.bar', 100, { force: false })
del({}, 'foo.bar', { force: false })
immutableSet({}, 'foo.bar', 100, { force: false })
immutableDel({}, 'foo.bar', { force: false })
// TypeError: Cannot read property 'bar' of undefined (at keypath 'foo' of 'foo.bar')
get({ foo: {} }, 'foo.bar', { force: false })
set({ foo: {} }, 'foo.bar', 100, { force: false })
del({ foo: {} }, 'foo.bar', { force: false })
immutableSet({ foo: {} }, 'foo.bar', 100, { force: false })
immutableDel({ foo: {} }, 'foo.bar', { force: false })
// TypeError: Cannot read property 'bar' of undefined (at keypath 'foo.bar' of 'foo.bar.qux')
hasKeypath({}, 'foo.bar', { force: false })
// TypeError: Cannot read property 'hasOwnProperty' of undefined (hasOwnProperty('bar') errored at keypath 'foo' of 'foo.bar')
keypathIn({}, 'foo.bar', { force: false })
// TypeError: Cannot use 'in' operator to search for 'bar' in undefined (at 'foo' of 'foo.bar')
keypathIn({}, 'foo.bar.qux', { force: false })
hasKeypath({}, 'foo.bar.qux', { force: false })
// TypeError: Cannot read property 'bar' of undefined (at keypath 'foo' of 'foo.bar.qux')
/* Warnings for set and immutable-set */
// by default, set will overwrite primitives (string, number or regexp) to an object or array.
// when overwritePrimitives is set to false, sets will warn when settings a key on a primitive
// to disable all warnings use the option { warn: false }
set({}, '[0]', 'val', { overwritePrimitives: false })
// log: Setting number key (0) on object at keypath '' of '[0]')
set([], 'key', 'val', { overwritePrimitives: false })
// log: Setting string key 'foo' on array at keypath '' of 'foo')
set({ foo: 1 }, 'foo.qux', 'val', { overwritePrimitives: false })
// log: Setting key 'qux' on number 1 at keypath 'foo' of 'foo.qux')
set({ foo: 1 }, 'foo[0]', 'val', { overwritePrimitives: false })
// log: Setting number key (0) on number 1 at keypath 'foo' of 'foo[0]')
set({ foo: 'str' }, 'foo.bar', 'val', { overwritePrimitives: false })
// log: Setting key 'bar' on string 'str' at keypath 'foo' of 'foo.bar')
set({ foo: {} }, 'foo[0]', 'val', { overwritePrimitives: false })
// log: Setting number key (0) on object at keypath 'foo' of 'foo[0]')
/* Invalid keypaths */
get({}, 'foo.1bar')
// Error: Unexpected token '1' in keypath 'foo.1bar' at position 4 (invalid dot key)
get({}, 'foo[]')
// Error: Unexpected token ']' in keypath 'foo[]' at position 4 (invalid bracket key)
get({}, 'foo["]')
// Error: Unexpected token ']' in keypath 'foo[]' at position 5 (invalid bracket string key)
get({}, 'foo.')
// Error: Unexpected end of keypath 'foo.' (invalid dot key)
get({}, 'foo[')
// Error: Unexpected end of keypath 'foo[' (invalid bracket key)
get({}, "foo['")
// Error: Unexpected end of keypath 'foo['' (invalid bracket string key)
Documentation
GET
Returns value at keypath in obj
- @param {any} obj - context to read keypath from
- @param {string} keypath - bracket and/or dot notation keypath string
- @param {?object} opts - optional, defaults to { force: true }
- opts.force - force specifies whether non-existant keypaths should be ignored, defaults to true
if false, `get` will error when reading a key on a non-existant keypath.
- @returns {any} value at keypath
const get = require('keypather/get');
const obj = {
foo: {
bar: {
baz: 'val'
}
}
};
get(obj, "foo.bar.baz"); // returns 'val'
get(obj, "foo['bar'].baz"); // returns 'val'
get(obj, "['foo']['bar']['baz']"); // returns 'val'
get({}, 'foo.two.three', { force: false }) // throws error
// TypeError: Cannot read property 'three' of undefined (at keypath 'foo.two' of 'foo.two.three')
SET
Sets a value in obj at keypath. If force=true, set will create objects at non-existant keys in the keypath. If the non-existant key is a number, its value will be initialized as an array.
- @param {any} obj - context to read keypath from
- @param {string} keypath - bracket and/or dot notation keypath string to read from obj
- @returns {any} value - value to set at keypath
- @param {?object} opts - optional, defaults to { force: true, overwritePrimitives: true, warn: true }
- opts.force - whether non-existant keys in keypath should be created, defaults to true.
if false, `set` will error when reading a key on a non-existant keypath.
- opts.overwritePrimitives - whether primitive keys (booleans, strings, numbers) should be overwritten.
setting a key on a primitive will convert it to an object or array (if key is string or number).
if false, `set` will log a warning when setting keys on primitives.
- opts.silent - specifies whether warning logs should be enabled, defaults to false.
- @returns {any} value set at keypath
const set = require('keypather/set');
let obj = {
foo: {
bar: {
baz: 'val'
}
}
};
set(obj, "foo['bar'].baz", 'val'); // returns 'val'
set(obj, "foo.bar.baz", 'val'); // returns 'val'
set(obj, "['foo']['bar']['baz']", 'val'); // returns 'val'
/* By default, set forces creation of non-existant keys */
obj = {}
set(obj, "foo.bar.baz", 'val'); // returns 'val'
// obj becomes:
// {
// foo: {
// bar: {
// baz: 'val'
// }
// }
// };
/* By default, overwrites primitives when setting a key on one */
obj = { foo: 1 }
set(obj, "foo.bar.baz", 'val'); // returns 'val'
// obj becomes:
// {
// foo: {
// bar: {
// baz: 'val'
// }
// }
// };
obj = { foo: 1 }
set(obj, "foo[0].baz", 'val'); // returns 'val'
// obj becomes:
// {
// foo: [{
// baz: 'val'
// }]
// };
/* Errors, force=false */
set({}, "foo.bar.baz", 'val', { force: false }); // throw's an error
// TypeError: Cannot read property 'bar' of undefined (at keypath 'foo' of 'foo.bar.baz')
// see more errors above in the 'Errors' section
/* Warnings, overwritePrimitives=false */
set({ foo: 'str' }, 'foo.bar', 'val', { overwritePrimitives: false })
// log: Setting key 'bar' on string 'str' at keypath 'foo' of 'foo.bar')
// see more warnings above in the 'Errors' section
DEL
Deletes value a keypath in obj. Similar to delete obj.key
.
- @param {any} obj - context to read keypath from
- @param {string} keypath - bracket and/or dot notation keypath string to delete from obj
- @param {?object} opts - optional, defaults to { force: true }
- opts.force - whether non-existant keys in keypath should be created, defaults to true.
if false, `del` will error when reading a key on a non-existant keypath.
- @returns {boolean} true except when the property is non-configurable or in non-strict mode
const del = require('keypather/del');
const obj = {
foo: {
bar: {
baz: 'val'
}
}
};
del(obj, "foo['bar'].baz"); // true
del(obj, "foo.bar.baz"); // true
del(obj, "['foo']['bar']['baz']"); // true
// obj becomes:
// {
// foo: {
// bar: {}
// }
// }
/* Errors, force=false */
del(obj, "one.two.three", 'val', { force: false }); // throw's an error
// TypeError: Cannot read property 'two' of undefined (at keypath 'one' of 'one.two.three')
// see more errors above in the 'Errors' section
IMMUTABLE SET
Sets a value in obj at keypath. If force=true, set will create objects at non-existant keys in the keypath. If the non-existant key is a number, its value will be initialized as an array.
- @param {any} obj - context to read keypath from
- @param {string} keypath - bracket and/or dot notation keypath string to read from obj
- @returns {any} value - value to set at keypath
- @param {?object} opts - optional, defaults to { force: true, overwritePrimitives: true, warn: true }
- opts.force - whether non-existant keys in keypath should be created, defaults to true.
if false, `immutable-set` will error when reading a key on a non-existant keypath.
- opts.overwritePrimitives - whether primitive keys (booleans, strings, numbers) should be overwritten.
setting a key on a primitive will convert it to an object or array (if key is string or number).
if false, `immutable-set` will log a warning when setting keys on primitives.
- opts.silent - specifies whether warning logs should be enabled, defaults to false.
- opts.shallowClone - provide custom shallowClone, defaults to shallow-clone
- @returns {any} returns same obj if unmodified, otherwise modified clone of obj
const set = require('keypather/immutable-set');
let obj = {
foo: {
bar: {
baz: 'val'
}
}
};
let out
out = set(obj, "foo['bar'].baz", 'val'); // returns SAME object, since the value was unchanged
// out === obj
out = set(obj, "foo.bar.baz", 'val2'); // returns { foo: { bar: { baz: 'val2' } } } (new object)
// out !== obj
out = set(obj, "['foo']['bar']['baz']", 'val3'); // returns { foo: { bar: { baz: 'val3' } } } (new object)
// out !== obj
/* By default, overwrites primitives when setting a key on one */
obj = { foo: 1 }
out = set(obj, "foo.bar.baz", 'val'); // returns new object
// out !== obj
// out is:
// {
// foo: {
// bar: {
// baz: 'val'
// }
// }
// };
obj = { foo: 1 }
out = set(obj, "foo[0].baz", 'val'); // returns new object
// out !== obj
// out is:
// {
// foo: [{
// baz: 'val'
// }]
// };
/* Errors, force=false */
obj = {}
set(obj, "foo.bar.baz", 'val', { force: false }); // throws error
// Error: Cannot read property 'bar' of undefined (at keypath 'foo' of 'foo.bar.baz')
/* Warnings, force=false */
obj = { foo: 'str' }
out = set(obj, 'foo.bar', 'val', { overwritePrimitives: false })
// out === obj, since keys cannot be set on strings or numbers
// log: Setting key 'bar' on string 'str' at keypath 'foo' of 'foo.bar')
IMMUTABLE DEL
Deletes value a keypath in obj. Similar to delete obj.key
.
- @param {any} obj - context to read keypath from
- @param {string} keypath - bracket and/or dot notation keypath string to delete from obj
- @param {?object} opts - optional, defaults to { force: true }
- opts.force - whether non-existant keys in keypath should be created, defaults to true.
if false, `del` will error when reading a key on a non-existant keypath.
- opts.shallowClone - provide custom shallowClone, defaults to shallow-clone
- @returns {any} returns same obj if unmodified, otherwise modified clone of obj
const del = require('keypather/immutable-del');
const obj = {
foo: {
bar: {
baz: 'val'
}
}
};
let out
out = del(obj, "foo['bar'].baz"); // true
out = del(obj, "foo.bar.baz"); // true
out = del(obj, "['foo']['bar']['baz']"); // true
// obj becomes:
// {
// foo: {
// bar: {}
// }
// }
/* Errors, force=false */
del(obj, "one.two.three", 'val', { force: false }); // throw's an error
// Error: Cannot read property 'two' of undefined (at keypath 'one' of 'one.two.three')
IN
Returns true if keypath is "in" the obj at the keypath. Similar to "in" operator.
- @param {any} obj - context to read keypath in
- @param {string} keypath - bracket and/or dot notation keypath string to read from obj
- @param {?object} opts - optional, defaults to { force: true }
- opts.force - force specifies whether non-existant keypaths should be ignored, defaults to true
- @returns {boolean} true if the keypath is "in" the obj, else false
const keypathIn = require('keypather/in');
const obj = {
foo: {
bar: {
baz: 'val'
__proto__: {
qux: 'val'
}
}
}
};
keypathIn(obj, "foo.bar.baz"); // true
keypathIn(obj, "foo.bar.qux"); // true
keypathIn(obj, "foo.bar.bing"); // false
keypathIn(obj, "foo['bar'].baz"); // true
keypathIn(obj, "one.two.three"); // false
// Errors, force=false
keypathIn(obj, "one.two.three", { force: false });
// Error: Cannot read property 'two' of undefined (at keypath 'two' of 'one.two.three')
keypathIn(obj, "foo.two.three", { force: false });
// TypeError: Cannot use 'in' operator to search for 'three' in undefined (at 'foo.two' of 'foo.two.three')
HAS
Returns true if the obj has the keypath. Similar to obj.hasOwnProperty
.
- @param {any} obj - context to read keypath in
- @param {string} keypath - bracket and/or dot notation keypath string to read from obj
- @param {?object} opts - optional, defaults to { force: true }
- opts.force - force specifies whether non-existant keypaths should be ignored, defaults to true
- @returns {boolean} true if the keypath is "in" the obj, else false
const hasKeypath = require('keypather/has');
const obj = {
foo: {
bar: {
baz: 'val'
__proto__: {
qux: 'val'
}
}
}
};
hasKeypath(obj, "foo.bar.baz"); // true
hasKeypath(obj, "foo.bar.qux"); // false
hasKeypath(obj, "['foo']['bar']['baz']"); // true
hasKeypath(obj, "one.two.three"); // false
// Errors, force=false
hasKeypath(obj, "one.two.three", { force: false }); // throw's an error
// Error: Cannot read property 'two' of undefined (at keypath 'two' of 'one.two.three
hasKeypath(obj, "foo.two.three", { force: false });
// Error: Cannot read property 'hasOwnProperty' of undefined (hasOwnProperty('three') errored at keypath 'foo.two' of 'foo.two.three')
FLATTEN
Flatten an object or array into a keypath object
- @param {any} obj - object or array to flatten
const flatten = require('keypather/flatten');
flatten({
foo: {
qux: 'hello'
},
bar: [
1,
{
yolo: [1]
}
]
});
// returns:
// {
// 'foo.qux': 'hello',
// 'bar[0]': 1,
// 'bar[1].yolo[0]': 1
// }
/* accepts a delimiter other than '.' as second arg */
flatten({
foo: {
qux: 'hello'
}
}, '_');
// returns:
// {
// 'foo_qux': 'hello',
// }
EXPAND
Expand a flattened object back into an object or array
- @param {any} obj - flattened object or array to be expanded
const expand = require('keypather/expand');
expand({
'foo.qux': 'hello',
'bar[0]': 1,
'bar[1].yolo[0]': 1
});
// returns:
// {
// foo: {
// qux: 'hello'
// },
// bar: [
// 1,
// {
// yolo: [1]
// }
// ]
// }
/* expand will assume an object is an array if any of the keys are numbers */
expand({
'[0]': 1,
'[1].yolo[0]': 1
});
// returns:
// [
// 1,
// {
// yolo: [1]
// }
// ]
/* accepts a delimiter other than '.' as second arg */
expand({
'foo_qux': 'hello'
}, '_');
// returns:
// {
// foo: {
// qux: 'hello'
// }
// }
Changelog
License
MIT
6 years ago
7 years ago
8 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago