- What is it?
- Installation
- What does it do?
- API summary
- Immutable comparison
- Test cases / code example
- Use cases for 'deep-mutation'
- Live TODO Example in Codesandbox
- Examples
It is a simple function which gets an object and a list of changes and returns a new updated object.
Since the version 2.0.0 deep-mutation returns a new object only when something was changed
npm install --save deep-mutation
constobj={a: 10,b: 20,c: {c1: 1,c2: 2,c3: {c31: 31}}};constresult={
...obj,c: {
...obj.c,c3: {
...obj.c3,c32: 25}}};import{mutate,mutateDeep,deepPatch}from'deep-mutation';constresultMutate=mutate(obj,{'c.c3.c32': 25});// ORconstresultMutate=mutate(obj,[['c.c3.c32',25]]);// OR since v2.1.0constresultMutate=mutate(obj,[[['c','c3','c32'],25]]);// OR since v3.0.0constresultMutate=mutate.deep(obj,{c: {c3: {c32: 25}}});// OR since v3.0.0constresultMutate=mutate(obj,deepPatch({c: {c3: {c32: 25}}}));// OR since v3.3.1constresultMutate=mutateDeep(obj,{c: {c3: {c32: 25}}});import{mutate}from'deep-mutation';constmyObject={a: 100,b: 200,c: {c1: 1,c2: 2},d: []};constchanges=[['a',111],['b.b1',222],['b.b2','text'],['c.c1',20],['c.c2'],['d.[]',10],['d.[]',20],['e',[1,2,3]]];constresult=mutate(myObject,changes);constobj={a: 111,b: {b1: 222,b2: 'text'},c: {c1: 20},d: [10,20],e: [1,2,3]};Changes can be specified as an array of arrays or an object where each key is a path or object converted by deepPatch function:
// array of arraysconstchanges=[['a',111],['b.b1',222],['b.b2','text'],['c.c1',20],['c.c2'],['d.[]',10],['d.[]',20],['e',[1,2,3]]];OR
// objectconstchanges={a: 111,'b.b1': 222,'b.b2': 'text','c.c1': 20,'c.c2': undefined,'d.[+123412]': 10,'d.[+544555]': 20,e: [1,2,3]};OR
// deep patchimport{deepPatch}from'deep-mutation';constchanges=deepPatch({a: 111,b: {b1: 222,b2: 'text'},c: {c1: 20,c2: undefined},d: {'[+123412]': 10,'[+544555]': 20},e: [1,2,3]});If a key for an array item starts from +, then the value will be appended to the end of the array like [].push().
constpatch={'arr.[+123312312]': 100,// OR'arr.[+6]': 100,// will be equal to'arr.[]': 100,};It is useful when you need to add some items to an array and use changes as an Object.
import{mutate}from'deep-mutation';// ...returnmutate({arr: []},{// It is an error because JS object can't have values with the same keys!// 'arr.[]': 1,// 'arr.[]': 2,//It is the correct way'arr.[+1123]': 1,'arr.[+232]': 2,'arr.[+43534]': 3,'arr.[+64]': 4,'arr.[]': 5,});// the result will be = { arr: [1,2,3,4,5] }If a key for an array item starts from = ([=10] or [=data.id=99]), then index will be found by comparison item or item's property and value. [=field.path=value].
import{mutate}from'deep-mutation';// ...returnmutate({arr: [1,2,3,4,5,6,7,8]},{'arr.[=2]': 200,// index for element with value `2` will be `1`'arr.[=8]': 800,// index for element with value `8` will be `7`'arr.[=100]': 'undefined',// `100` is not found in arr and will be ignored, index is `-1`});// the result will be = { arr: [1,200,3,4,5,6,7,800] }arr.[=] or arr.[=value=] - to find empty string value in array (or item.value = '')
arr.[=false] or arr.[=value=false] - to find false value in array (or item.value = false)
Example for objects
import{mutate}from'deep-mutation';// ...returnmutate({arr: [{id: 10},{id: 20}]},{'arr.[=id=20].name': 'Name 20',// index for element with `id=20` will be `1`'arr.[=id=999]': 'undefined',// it is not found, ignored});// the result will be = { arr: [{ id: 10 }, { id: 20, name: 'Name 20' }] }Example with deep path
import{mutate}from'deep-mutation';// ...returnmutate({arr: [{data: {id: 12}},{data: {id: 30}}]},{'arr.[=data.id=12].data.v': 'value1',// index for element with `data.id=12` will be `0`'arr.[=data.id=999]': 'undefined',// it is not found, ignored});// the result will be = { arr: [{ data: { id: 12, v: 'value1' }}, { data: { id: 30 }}] }It will be ignored.
import{mutate}from'deep-mutation';// ...returnmutate({arr: [1,2,3,4]},{'arr.[-1]': 999});// the result will be = { arr: [1,2,3,4] }It will insert element onto provided position.
import{mutate}from'deep-mutation';// ...returnmutate({arr: [1,2,3,4]},{'arr.[>2]': 555},{'arr.[>3]': 999},);// the result will be = { arr: [1,2,555,999,3,4] }mutate.deep(sourceObject: Object, patchObject: Object): Object - returns new object with deep patched result. The same that mutateDeep.
import{mutate}from'deep-mutation';returnmutate.deep({a: 10,b: {b1: 1,b2: 2}},// main object{c: 50,b: {b2: 100}}// changes);// result = { a: 10, b: { b1: 1, b2: 100 }, c: 50}OR
import{mutateDeep}from'deep-mutation';returnmutateDeep({a: 10,b: {b1: 1,b2: 2}},// main object{c: 50,b: {b2: 100}}// changes);// result = { a: 10, b: { b1: 1, b2: 100 }, c: 50}OR
deepPatch(patchObject: Object): PatchObject - returns instruction for deep patching
importmutate,{deepPatch}from'deep-mutation';returnmutate({a: 10,b: {b1: 1,b2: 2}},// main objectdeepPatch({c: 50,b: {b2: 100}})// changes);// result = { a: 10, b: { b1: 1, b2: 100 }, c: 50}If deep-mutation function is called only with one argument (an object without changes) then it will return a function which can take one argument as changes. When called, it will save the changes and return an updated object.
import{mutate}from'deep-mutation';constpatch=mutate({a: 1,b: 2});constresult1=patch({c: 3});// result1 === { a: 1, b: 2, c: 3}constresult2=patch({d: 4});// result2 === { a: 1, b: 2, c: 3, d: 4}constresult3=patch();// result3 === result2 === { a: 1, b: 2, c: 3, d: 4}| Function | Description |
|---|---|
mutate(obj, changes) | Main function. It takes object and changes to apply, returns new object |
mutate.deep(obj, patch) | Deep objec upgrade. Aliase: mutateDeep |
mutateDeep(obj, patch) | Aliase for mutate.deep |
deepPatch(obj) | Translate flat object to deep update instruction for mutate |
In order to use dots in the path of changes you should use the path as an Array of keys:
mutate({ a: { 'a.b': { 'a.b.c': 10 }} }, [[['a', 'a.b', 'a.b.c'], newValue]])
OR
mutate({ a: { 'a.b': { 'a.b.c': 10 }} }, [['a-a.b-a.b.c'.split('-'), newValue]])
import{mutate}from'deep-mutation';constobj={a: {'a.1': {'a.1.1': 100}}};constchanges=[['a-a.1-a.1.1'.split('-'),15]]constresult=mutate(obj,changes);// result === { a: { a.1: { a.1.1: 15 }}}Sandbox editor: https://codesandbox.io/s/l9ovomzv99
Sandbox view: https://l9ovomzv99.csb.app/
Sandbox editor: https://codesandbox.io/s/j4wkq2znj5
Sandbox view: https://j4wkq2znj5.codesandbox.io/
mutate({a: 10},[['a',5]]);// { a: 5 }mutate({a: 10},[['b',5]]);// { a: 10, b: 5 }mutate({},[['a',10],['b',5]]);// { a: 10, b: 5 }mutate({a: 10},[['a']]);// { }mutate({a: 10},[null]);// { a: 10 }mutate({a: 10},[['a'],['b']]);// { } — same as abovemutate({a: 10},[['a',[1,2,3]]]);// { a: [1,2,3] }mutate({a: 10},[['a',{aa: 1}]]);// { a: { aa: 1 }}mutate({a: 10},[['a',5],['b',{bb: 2}]]);// { a: 5, b: { bb: 2 }}// extend an objectmutate({a: {aa: 10}},[['a.aa',5]]);// { a: { aa: 5 }}mutate({a: {aa: 10}},[['a.aa']]);// { a: { }}mutate({a: {aa: {aaa: 10}}},[['a.aa'],['a.aa.aaa']])// { a: { }}mutate({a: {aa: 10}},[['a.aa.[]',1]]);// { a: { aa: [1] }}mutate({a: {aa: 10}},[['a.aa'],['a']]);// { }mutate({a: {aa: 10}},['a.aa','a']);// { }mutate({a: 10},[['a.aa',5]]);// { a: { aa: 5 }}mutate({a: 10},[['a.aa.aaa',5]]);// { a: { aa: { aaa: 5 }}}mutate({a: 10},[['a.aa.aaa',5],['a.aa.aaa.aaaa',2]]);// { a: { aa: { aaa: { aaaa: 2 }}}}mutate({a: 10},[['a.aa',5],['a.aa2',2]]);// { a: { aa: 5, aa2: 2 }}mutate({a: 10},[['a.aa',5],['b.bb',2]]);// { a: { aa: 5 }, b: { bb: 2 } }// extend an arraymutate([],[['[]',5]]);// [5]mutate({a: []},[['a.[]',5]]);// { a: [5] }mutate({a: []},[['a.[0]',5]]);// { a: [5] }mutate({a: []},[['a[0]',5]]);// { a: [5] }mutate({a: []},[['a[][]',5]]);// { a: [[5]] }mutate({a: []},[['a.[].[]',5]]);// { a: [[5]] }mutate({a: []},[['a.[2]',5]]);// { a: [undefined, undefined, 5] }mutate({a: [1]},[['a.[]',5]]);// { a: [1, 5] }mutate({a: [1]},[['a.[]',5],['a.[]',7]]);// { a: [1, 5, 7] }mutate({a: [1]},[['a.[0]',5]]);// { a: [5] }mutate({a: [1]},[['a.[0]']]);// { a: [] }// changes as an objectmutate({a: []},{'a.[]': 5});// { a: [5] }mutate({a: []},{'a.[0]': 5});// { a: [5] }mutate({a: []},{'a.[2]': 5});// { a: [undefined, undefined, 5] }mutate({a: [1]},{'a.[]': 5});// { a: [1, 5] }mutate({a: [1]},{'a.[0]': 5});// { a: [5] }mutate({a: {aa: 10}},{'a.aa': 5});// { a: { aa: 5 }}mutate({a: {aa: 10}},{'a.aa': undefined,'a.aaa': 99});// { a: { aaa: 99 }}mutate({},{'a.aa.aaa': undefined});// { }mutate({},[['a.aa.aaa']]);// { }mutate({a: {0: 'v0',1: 'v1'}},[['a.0']]);// { a: { 1: 'v1' }}mutate({a: [1,2,3]},[['a.[]']]);// { a: [1,2,3] }mutate({a: [1,2,3]},[['a.[0]']]);// { a: [2,3] }mutate({a: [1,2,3]},[['a.0']]);// { a: [undefined, 2,3] }// set the object, extend the objectmutate({},[['a',{aa: 5}]])// { a: { aa: 5 }}mutate({a: 10},[['a',{aa: 5}]]);// { a: { aa: 5 }}mutate({a: 10},[['a',{aa: {aaa: 5}}]])// { a: { aa: { aaa: 5 }}}mutate({a: 10},[['a',{aa: {aaa: 5}}]]);// { a: { aa: { aaa: 5 }}}mutate({a: 10},[['a',{aa: {aaa: 5}}],['a.aa.aaa2',1]]);// { a: { aa: { aaa: 5, aaa2: 1 }}}mutate({a: 10},[['a',{aa: {aaa: 5,aaa2: 1}}],['a.aa.aaa2']]);// { a: { aa: { aaa: 5 }}}mutate({a: 10},[['a',{aa: 5}],['a',[1,2,3]]])// { a: [1,2,3] }mutate({a: 10},[['a',{aa: 5}],['a.aa',12]])// { a: { aa: 12 }}mutate({b: 20},[['a',{aa: 5}],['a']])// { b: 20 }mutate({b: 20},[['a',{aa: 5}],['a.aa']])// { a: { }, b: 20 }mutate({a: 10,b: [],c: {}},{a: 50,b: {b1: 10},c: [1,2,3]})// { a: 50, b: { b1: 10 }, c: [1,2,3] }mutate({a: 10,b: [],c: {},d: {d1: 12},e: [9,8,7]},{a: 50,b: {b1: 10},c: [1,2,3],'c.[]': {cc: 22},'b.b2': 17,'d.d2': 15,'e.[0]': 1,'e.[]': 3})/*{ a: 50, b: { b1: 10, b2: 17 }, c: [1,2,3, { cc: 22 }], d: { d1: 12, d2: 15 }, e: [1,8,7,3]}*/mutate({a: {a1: {a1_1: 22}},b: [{b1: 10}],c: [{c1: 1}]},{'a.a1.a1_1': 33,'a.a1.a1_2': 9,'a.a2': 14,'b.[0].b1': 11,'b.[]': 15,'b.[0].b2': null,'c[0].c1': undefined,'c[0]': 7})/*{ a: { a1: { a1_1: 33, a1_2: 9 }, a2: 14 }, b: [{ b1: 11, b2: null }, 15], c: [7]}*/mutate({a: 10,b: 20},{a: {a1: 1,a2: 2},'a.a3.a3_1': 20,b: [1,2,3,{b1: 1}],'b.[]': 11,'b[3].b2.b2_1.b2_1_1': 'b2_1_1 value','c.[]': 14})/*{ a: { a1: 1, a2: 2, a3: { a3_1: 20 } }, b: [ 1,2,3, { b1: 1, b2: { b2_1: { b2_1_1: 'b2_1_1 value' } } }, 11 ], c: [14]}*/constobj={a: 10};constresult=mutate(obj,[]);expect(result).toBe(obj);If you use an instance of Object (or Array) to construct a list of changes then this instance of Object (or Array) will be changed.
test('should change object value',()=>{constobj={b: []};constpatchObject={b1: 1,b2: 2,b3: 3};constchanges=[['b',patchObject],['b.b4',4]];constresut=mutate(obj,changes);expect(resut.b).toEqual(patchObject);expect(resut.b).toBe(patchObject);expect(patchObject).toEqual({b1: 1,b2: 2,b3: 3,b4: 4});});test('should change array value',()=>{constobj={b: [5,6]};constpatchArray=[1,2,3];constchanges=[['b',patchArray],['b.[]',4]];constresut=mutate(obj,changes);expect(resut.b).toEqual(patchArray);expect(resut.b).toBe(patchArray);expect(patchArray).toEqual([1,2,3,4]);});import{mutate}from'deep-mutation';exportdefault(state={},action)=>{const{ type, payload }=action;const{ uid }=payload;switch(type){case'API_REQUEST':
returnmutate(state,[[`${uid}._status`,'request']]);case'API_REQUEST__OK':
returnmutate(state,[[`${uid}._status`,'success'],[`${uid}.result`,payload.body],]);case'API_REQUEST__FAIL':
returnmutate(state,[[`${uid}._status`,'fail'],[`${uid}.result`],[`${uid}.error`,payload.error]]);default:
returnstate;}};// ...import{mutate}from'deep-mutation';classExampleComponentextendsComponent{// ...onClick=()=>this.setState(state=>mutate(state,{isFetching: true,'data.value': 'default','data.key': null,validation: null}));// ...}
