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 leanconst get = const set = const del = const immutableSet = const immutableDel = const keypathIn = const hasKeypath = const expand = const flatten =
GET, SET, DEL Example
const get = const set = const del = let obj // Objectsobj = foo: bar: 100 // returns 100 // returns true, obj becomes { foo: {} } // returns 200, obj becomes { foo: { bar: { qux: 200 } } } // returns 200 // Arraysobj = {} // obj is { foo: [ 100 ] }
Immutable SET, DEL Example
const set = const del = let objlet out // Objectsobj = foo: bar: 100 out = // returns obj// out === obj,// since it was not modifiedout = // returns { foo: { bar: { qux: 200 } } }// out !== obj,// obj is still { foo: { bar: 100 } }out = // returns obj// out === obj,// since it was not modifiedout = // returns { foo: { bar: {} } }// out !== obj,// obj is still { foo: { bar: { qux: 200 } } } // Arraysobj = {}out = // returns { foo: [ 100 ] } (new)// out !== obj, obj is still { foo: { bar: 100 } }
HAS, IN Example
const hasKeypath = const keypathIn = const obj = foo: Object // returns false (bar is on proto) // returns true // returns true
FLATTEN, EXPAND Example
const expand = const flatten = const obj = // obj is { foo { bar: 1, qux: [ 100, 200, wut: 'val' ] } }const flat = // flat is { 'foo.bar': 1, 'foo.qux': 2 } }
Errors Example
/* Missing deep values w/ "force: false" */// TypeError: Cannot read property 'bar' of undefined (at keypath 'foo' of 'foo.bar')// TypeError: Cannot read property 'bar' of undefined (at keypath 'foo.bar' of 'foo.bar.qux')// TypeError: Cannot read property 'hasOwnProperty' of undefined (hasOwnProperty('bar') errored at keypath 'foo' of 'foo.bar')// TypeError: Cannot use 'in' operator to search for 'bar' in undefined (at 'foo' of 'foo.bar')// 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 }// log: Setting number key (0) on object at keypath '' of '[0]')// log: Setting string key 'foo' on array at keypath '' of 'foo')// log: Setting key 'qux' on number 1 at keypath 'foo' of 'foo.qux')// log: Setting number key (0) on number 1 at keypath 'foo' of 'foo[0]')// log: Setting key 'bar' on string 'str' at keypath 'foo' of 'foo.bar')// log: Setting number key (0) on object at keypath 'foo' of 'foo[0]') /* Invalid keypaths */// Error: Unexpected token '1' in keypath 'foo.1bar' at position 4 (invalid dot key) // Error: Unexpected token ']' in keypath 'foo[]' at position 4 (invalid bracket key)// Error: Unexpected token ']' in keypath 'foo[]' at position 5 (invalid bracket string key)// Error: Unexpected end of keypath 'foo.' (invalid dot key)// Error: Unexpected end of keypath 'foo[' (invalid bracket key)// 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 = ;const obj = foo: bar: baz: 'val' ;; // returns 'val'; // returns 'val'; // returns 'val' // 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 = ; let obj = foo: bar: baz: 'val' ;; // returns 'val'; // returns 'val'; // returns 'val' /* By default, set forces creation of non-existant keys */obj = {}; // returns 'val'// obj becomes:// {// foo: {// bar: {// baz: 'val'// }// }// }; /* By default, overwrites primitives when setting a key on one */obj = foo: 1 ; // returns 'val'// obj becomes:// {// foo: {// bar: {// baz: 'val'// }// }// };obj = foo: 1 ; // returns 'val'// obj becomes:// {// foo: [{// baz: 'val'// }]// }; /* Errors, 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 */// 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 = ; const obj = foo: bar: baz: 'val' ;; // true; // true; // true// obj becomes:// {// foo: {// bar: {}// }// } /* Errors, 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 = ; let obj = foo: bar: baz: 'val' ;let outout = ; // returns SAME object, since the value was unchanged// out === objout = ; // returns { foo: { bar: { baz: 'val2' } } } (new object)// out !== objout = ; // returns { foo: { bar: { baz: 'val3' } } } (new object)// out !== obj /* By default, overwrites primitives when setting a key on one */obj = foo: 1 out = ; // returns new object// out !== obj// out is:// {// foo: {// bar: {// baz: 'val'// }// }// };obj = foo: 1 out = ; // returns new object// out !== obj// out is:// {// foo: [{// baz: 'val'// }]// }; /* Errors, force=false */obj = {}; // throws error// Error: Cannot read property 'bar' of undefined (at keypath 'foo' of 'foo.bar.baz') /* Warnings, force=false */obj = foo: 'str' out = // 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 = ;const obj = foo: bar: baz: 'val' ;let outout = ; // trueout = ; // trueout = ; // true// obj becomes:// {// foo: {// bar: {}// }// } /* Errors, 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 = ;const obj = foo: bar: baz: 'val' __proto__: qux: 'val' ;; // true; // true; // false; // true; // false // Errors, force=false;// Error: Cannot read property 'two' of undefined (at keypath 'two' of 'one.two.three');// 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 = ;const obj = foo: bar: baz: 'val' __proto__: qux: 'val' ;; // true; // false; // true; // false // Errors, force=false; // throw's an error// Error: Cannot read property 'two' of undefined (at keypath 'two' of 'one.two.three;// 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 = ; ;// returns:// {// 'foo.qux': 'hello',// 'bar[0]': 1,// 'bar[1].yolo[0]': 1// } /* accepts a delimiter other than '.' as second arg */ ;// 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 = ; ;// returns:// {// foo: {// qux: 'hello'// },// bar: [// 1,// {// yolo: [1]// }// ]// } /* expand will assume an object is an array if any of the keys are numbers */ ;// returns:// [// 1,// {// yolo: [1]// }// ] /* accepts a delimiter other than '.' as second arg */ ;// returns:// {// foo: {// qux: 'hello'// }// }