Overview

Resolves paths in Javascript objects, providing tools to get, set, replace, or remove the resolved item.

Install

Web Browser

<script src="https://unpkg.com/js-path-resolver/dist/index.global.js"></script>
<script>
const resolver = jsPathResolver.default;
</script>

Node

import resolver from  'js-path-resolver';

Usage

import resolver from 'js-path-resolver'

const state = {
    lists:{
        todo:[
            {caption:'wake up', completed:true},
            {caption:'eat', completed:false}
        ]
    }
}

const info = resolver(state, 'lists.todo.1.completed')
const value = info.get()
info.set(true)

// Remove a field from its parent object or array.
resolver(state, 'lists.todo.1').delete()

By default, if a path cannot be resolved up to the parent, an exception will be thrown.

resolver(state, 'foo') // will not throw
resolver(state, 'foo.bar') // will throw

To never throw, use the onError option. The property exists will be set to false instead. However, all mutation operations (delete, get, set) will throw.

const info = resolver(state, 'foo.bar', {onError:'continue'}) // will not throw
// info.exists === false
info.get() // will throw

API

const info = resolver(object, path, options)

Describing paths

A path uses dots to traverse the objects and arrays inside a Javascript object. An array item can be addressed with brackets, foo[2], or with a dot, foo.2.

Using the state object above, the completed value of the eat todo is at lists.todo.1.completed or lists.todo[1].completed.

A quoted bracket names a property exactly, so nothing inside it needs escaping: a["b.c"] and a['b c'] address the properties b.c and b c of a.

Paths that do not exist

The resolver always returns a path info object. If the last field in the path is not defined in its parent, info.field is undefined and nothing is thrown, so set can be used to add it. If the path cannot be followed as far as the parent, an Error is thrown, unless the onError or create option is set.

const info = resolver(state, 'lists.done') // does not throw, info.field is undefined
info.set([])                               // adds state.lists.done

Special characters

To use ., [, ] or \ in a property name, escape it with \ in the path. Inside a Javascript string literal the backslash itself has to be doubled.

// Resolves state['mr.dot']['odd[name']
const info = resolver(state, 'mr\\.dot.odd\\[name')

Path info methods

  • get() returns the field. Same as info.field.
  • set(newValue) replaces the field with newValue. Same as info.parent[info.fieldName] = newValue.
  • delete() removes the field from its parent. On an object this is the same as delete info.parent[info.fieldName]. On an array the item is spliced out, so the items after it move down and no hole is left.

set and delete refuse a path that passes through __proto__, prototype or constructor, because writing there changes a prototype shared by every object. Reading through them with get is allowed. set also throws if the parent is a string, number or other primitive.

delete on an array only splices when the field name is an index inside the array. An index past the end does nothing, and a name that is not an index is deleted as an ordinary property of the array.

Path info fields

  • field is the resolved value, or undefined.
  • parent is the object or array that holds the field.
  • fieldName is the name of the field in its parent.
  • fieldNames is an array of every field name in the path, starting from the root object.
  • exists is true if the path could be resolved up to the parent.
  • has is true if the field itself is present as an own property of its parent. A missing last field has exists true and has false.

Options

  • onError is 'throw', the default, or 'continue'. Any other value is rejected with a TypeError. 'continue' stops the resolver throwing when the path cannot be followed to the parent. exists is false instead, parent and field are null, and get, set and delete throw.
  • create set to true lets set build the objects and arrays that are missing along the path. An array is built where the next field is an index. Nothing is created until set is called, and it implies onError: 'continue'.
const state = {}
resolver(state, 'lists.todo[0].caption', { create: true }).set('wake up')
// state is { lists: { todo: [ { caption: 'wake up' } ] } }

TypeScript

Type declarations are included for all three entry points.

import resolver, { PathInfo, ResolveOptions } from 'js-path-resolver'

const info: PathInfo<boolean> = resolver<boolean>(state, 'lists.todo[1].completed')

Vue and Vuex

Two optional entry points. The main entry needs no framework and never did.

import { mutations, getters } from 'js-path-resolver/vuex'
import DotPathPlugin from 'js-path-resolver/vue-plugin'

mutations provides path.set, path.delete, path.clear, path.increment, path.decrement, path.toggle, path.true, path.false, path.list.add, path.list.remove, path.list.insert, path.list.replace and path.list.move for a Vuex store, and getters provides pathGet and pathExists. path.increment and path.decrement step by value, or by 1 without one. As of 2.0.0 this entry point imports nothing but the resolver: Vue 3's reactivity observes a plain assignment, so the Vue.set calls that Vue 2 required are gone, and these mutations work against any object.

DotPathPlugin is Vue 3 only and takes the store:

import { createStore } from 'vuex'
import { mutations, getters } from 'js-path-resolver/vuex'
import DotPathPlugin from 'js-path-resolver/vue-plugin'

const store = createStore({ state, mutations, getters })
app.use(store).use(DotPathPlugin, { store })
this.$dp('path.list.add', 'a.b', value)   // any mutation by name
this.$dp.set('a.b', value)                // shorthand for path.set

Outside a component, createDotPathDispatcher(store) returns the same function.

Passing the store is required because Vue 3 reads a global property with globalProperties[key], a plain access on that object, so nothing installed there can see the component that read it. The Vue 2 plugin found the store through the component; Vue 3 has no component prototype to hang that on.

Staying on Vue 2

Use 1.1.0, which is the last release with the Vue 2 plugin and the Vue.set mutations. It is not a deprecated version: it carries all six fixes listed in the changelog and the same licence.

License

MIT. Copyright (c) 2018-present Steven Spungin. See LICENSE.