changed.js
v1.0.0changed
A tiny (~1.5kb minified+gzipped) and fast, library for updating JavaScript objects and arrays directly.
Supports nested key paths via path arrays or dot-bracket syntax, and all methods are curriable (with placeholder support) for composability. Can support typical changes of view-model attributes by methods has
, get
, set
, remove
, add
and merge
with a small footprint.
Table of contents
Motivation
Objects with nested properties and arrays are used to store application state. Libraries like Backbone provide functions for inspecting and manipulating the state as methods of specialized objects like Backbone.Model
. This library provides only such methods without having to include the rest of the functionality of a bigger library. You can use other libraries to make your application complete; for example on-change for property change notifications.
This library has been inspired by unchanged, which has been used for the initial design and imnplementation. On the contrary to the immutable unchanged, this library supports mutable application states.
Installation
You will need node version >= 4 and npm to install and use this module:
npm install changed.js
Browser users will find non-minified and minified UMD-compliant scripts in the dist
directory.
Usage
import {has, get, set, remove, add, merge} from 'changed';
const object = {
foo: 'foo',
bar: [
{
baz: 'quz'
}
]
};
// handle standard properties
const foo = get('foo', object);
// or nested properties
const baz = set('bar[0].baz', 'not quz', object);
// all methods are curriable
const removeBaz = remove('bar[0].baz');
const sansBaz = removeBaz(object);
NOTE: There is no default
export, so if you want to import all methods to a single namespace you should use the import *
syntax:
import * as c from 'changed';
Methods
has
has(path: (Array<number|string>|number|string), object: (Array<any>|Object)): any
Checks if there is a property defined on the object
passed and on the path
specified.
const object = {
foo: [
{
bar: 'baz'
}
]
};
console.log(get('foo[0].bar', object)); // baz
console.log(get(['foo', 0, 'bar'], object)); // baz
get
get(path: (Array<number|string>|number|string), object: (Array<any>|Object)): any
Getter function for properties on the object
passed and on the path
specified.
const object = {
foo: [
{
bar: 'baz'
}
]
};
console.log(get('foo[0].bar', object)); // baz
console.log(get(['foo', 0, 'bar'], object)); // baz
set
set(path: (Array<number|string>|number|string), value: any, object: (Array<any>|object)): (Array<any>|Object)
Returns the object
passed, with the value
assigned to the final key on the path
specified.
const object = {
foo: [
{
bar: 'baz'
}
]
};
console.log(set('foo[0].bar', 'quz', object)); // {foo: [{bar: 'quz'}]}
console.log(set(['foo', 0, 'bar'], 'quz', object)); // {foo: [{bar: 'quz'}]}
remove
remove(path: (Array<number|string>|number|string), object: (Array<any>|object)): (Array<any>|Object)
Returns a new clone of the object
passed, with the final key on the path
removed if it exists.
const object = {
foo: [
{
bar: 'baz'
}
]
};
console.log(remove('foo[0].bar', object)); // {foo: [{}]}
console.log(remove(['foo', 0, 'bar'], object)); // {foo: [{}]}
add
add(path: (Array<number|string>|number|string), value: any, object: (Array<any>|object)): (Array<any>|Object)
Returns the object
passed, with the value
added at the path
specified. This can have different behavior depending on whether the item is an Object
or an Array
.
const object = {
foo: [
{
bar: 'baz'
}
]
};
// object
console.log(add('foo', 'added value' object)); // {foo: [{bar: 'baz'}, 'added value']}
console.log(add(['foo'], 'added value', object)); // {foo: [{bar: 'baz'}, 'added value']}
// array
console.log(add('foo[0].quz', 'added value' object)); // {foo: [{bar: 'baz', quz: 'added value'}]}
console.log(add(['foo', 0, 'quz'], 'added value', object)); // {foo: [{bar: 'baz', quz: 'added value'}]}
Notice that the Object
usage is idential to the set
method, where a key needs to be specified for assignment. In the case of an Array
, however, the value is pushed to the array at that key.
NOTE: If you want to add an item to a top-level array, pass null
as the key:
const object = ['foo'];
console.log(add(null, 'bar', object)); // ['foo', 'bar']
merge
merge(path: (Array<number|string>|number|string), value: any, object: (Array<any>|object)): (Array<any>|Object)
Returns the object
passed, after performing a deep merge with the value
(an object) at the path
specified.
const object1 = {
oneSpecific: 'value',
object: {
one: 'value1',
two: 'value2'
}
};
const object2 = {
one: 'new value',
three: 'value3'
};
console.log(merge('object', object2, object1)); // {oneSpecific: 'value', object: {one: 'new value', two: 'value1', three: 'value3'}}
NOTE: If you want to merge the entirety of both objects, pass null
as the key:
const object1 = {
oneSpecific: 'value',
object: {
one: 'value1',
two: 'value2'
}
};
const object2 = {
one: 'new value',
three: 'value3'
};
console.log(merge(null, object2, object1)); // {one: 'new value', oneSpecific: 'value', object: {one: 'value1', two: 'value1'}, three: 'value3'}
Development
Standard stuff, clone the repo and npm install
dependencies. The npm scripts available:
build
=> run webpack to build developmentdist
file with NODE_ENV=developmentbuild:minified
=> run webpack to build productiondist
file with NODE_ENV=productiondev
=> run webpack dev server to run example app / playgrounddist
=> runsbuild
andbuild-minified
lint
=> run ESLint against all files in thesrc
folderprepublish
=> runscompile-for-publish
prepublish:compile
=> runlint
,test:coverage
,transpile:es
,transpile:lib
,dist
test
=> run AVA test functions withNODE_ENV=test
test:coverage
=> runtest
but withnyc
for coverage checkertest:watch
=> runtest
, but with persistent watchertranspile:lib
=> run babel against all files insrc
to create files inlib
transpile:es
=> run babel against all files insrc
to create files ines
, preserving ES2015 modules (forpkg.module
)
Contributing
In lieu of a formal style guide, take care to maintain the existing coding style.
License
Copyright (c) 2017 Ferdinand Prantl