Object path

Problem

I want to reduce conditional assignment when setting nested keys in an object, ideally:

    {a:{b:{c:value}}} = set(a/b/c, value)

This is handy for data manipulation and abstracting path-based tools like LevelDB and Firebase Realtime Database.

Solution

Use object-path or lodash’s set/get.

Note: the tools mentioned above interpret numeric path segments as array indices, which may cause unexpected results when inserting arbitrary values, eg

    set(store, 'users.5.name', 'Kwan') // store.users.length --> 6

If this is an issue, consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function set(obj, path, val){
path.split('/').reduce((parent, key, i, keys) => {
if (typeof parent[key] != 'object') {
if (i === keys.length - 1) {
parent[key] = val
} else {
parent[key] = {}
}
}
return parent[key]
}, obj)
}
function get(obj, path){
return path.split('/').reduce((parent, key) => {
return typeof parent === 'object' ? parent[key] : undefined
}, obj)
}

Examples

Inverting an object:

1
2
3
4
5
6
7
8
const posts = {1: {tags: {sports: true, news: true}}, 2: {tags: {news: true}}}
const byTag = {}
Object.entries(posts).forEach(([id, post]) => {
Object.keys(post.tags).forEach(tag => {
set(byTag, `${tag}/${id}`, true)
})
})
// byTag --> { sports: { '1': true }, news: { '1': true, '2': true } }

Creating and querying a prefix tree:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const flatten = require('flat')

// populate tree
const emojis = {
'🙂': 'smile',
'😀': 'grinning',
'😁': 'grin'
}
const tree = {}
Object.entries(emojis).forEach(([emoji, name]) => {
let path = name.split('').join('/') + '/' + emoji
set(tree, path, true)
})

// lookup prefix
const prefix = 'g'
const path = prefix.split('').join('/')
const subtree = get(tree, path) || {}
const matches = Object.entries(flatten(subtree)).map(([key, val]) => {
return key.slice(-2)
})
console.log(matches) // --> ["😀", "😁"]

Feedback

Thoughts? Suggestions? Hit me up @erikeldridge

License

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 International License, and code samples are licensed under the MIT license.