Map an arbitrary JS structure by altering any portion of it, including itself.
constobj={a: [1,2,{value: 3},{child: [4,{value: 5}]}],b: 6,c: {value: 7}};constresult=deepMapper(obj,x=>{if(Array.isArray(x)){returnx.slice();}if(typeofx==="number"){return2**x;}return{ ...x};});// gives/*const obj = { a: [ 2, 4, { value: 8, }, { child: [ 16, { value: 32 } ] }, ], b: 64, c: { value: 128 }};*/The mapping is done as a pre-order traversal. This means that, if you change the parent by removing some of its children, the removed children won't be mapped.
deepMapper({child: {value: "x"}},item=>{if(isObject(item)){return{child: "I am changed"};}returnitem;});// gives/*{ child: 'I am changed'}*/Commonly, you'd want to map values in an immutable fashion:
deepMapper({nested: [1,'test',{bottom: true}]},item=>{if(Array.isArray(item)){returnitem.slice()// this will do a shallow copy of the nested array but not its values}if(item&&typeofitem==='object'){return{ ...item};// this will do a shallow copy of the top object, and the object with the 'bottom' key}// primitives in JS are immutable so we're fine herereturnitem;})The above will essentially perform a clone operation on the above object.
Here's how you'd write a deep clone function using deepmapper and shallow-clone to handle all possible values:
constdeepMapper=require("@enkidevs/deepmapper");constshallowClone=require("shallow-clone");constobj={a: [{b: "test"},1,/abc/g],c: false,d: newDate(2)};constcloned=deepMapper(obj,shallowClone);- Can map any primitive value
deepMapper(1,n=>n+1)===2// truedeepMapper('a',s=>s+'b')==='ab'// truedeepMapper(true,b=>!b)===false// true// ...- Handles circular references
// circular references get properly mappedconstobj={a: [1,2,3],b: {loop: null}};obj.b.loop=obj.a;constresult=deepMapper(obj,item=>{if(Array.isArray(item)){returnitem.slice();}if(item&&typeofitem==='object'){return{ ...item};}returnitem+1;})// gives/*{ a: [2, 3, 4], b: { loop: // points to the mapped result.a, not the original obj.a },};*/- Doesn't break on repeated references
constref={a: [1,2,3]};constarr=[ref,ref,{b: ref},{c: {value: ref}}];constresult=deepMapper(arr,item=>{if(Array.isArray(item)){returnitem.slice();}if(item&&typeofitem==='object'){return{ ...item};}returnitem+1;});// gives/*{ a: [2, 3, 4] },{ a: [2, 3, 4] },{ b: { a: [2, 3, 4] }},{ c: { value: { a: [2, 3, 4] }}},*/- Obfuscate MongoDB object by changing all MongoDB ObjectID
_idkeys toid:
functioncleanMongoId(item){if(Array.isArray(item)){returnitem.slice();}if(isObject(item)){if(ObjectId.isValid(item)){returnitem;}// only change the _id property if it's a valid ObjectIdif(ObjectId.isValid(item._id)){const{ _id, ...cleanItem}=item;if(_id){cleanItem.id=_id;}returncleanItem;}return{ ...item};}returnitem;}constid1=ObjectId();constid2=ObjectId();constdoc=[{nested1: {_id: id1,whatever: 5}},{nested2: {_id: id2,whatever: 5}},];constresult=deepMapper(doc,cleanMongoId);// gives/*[ { nested1: { id: id1, whatever: 5 }}, { nested2: { id: id2, whatever: 5 }},]*/- Changing state structures in a Redux reducer:
// ...conststate={items: [{id: 'todo-1',data: {createdAt: Date,updatedAt: Date,order: Number,text: 'yipikaye'}},// ...]}// ... somewhere in a reducer for action { type: 'CHANGE_UPDATED_AT' id: 'todo-1', updatedAt: new Date() }case[CHANGE_UPDATED_AT]:
returndeepMapper(state,chunk=>{if(Array.isArray(chunk)){returnchunk.slice();}if(chunk&&typeofchunk==='object'){if(chunk.id===action.id){return{...chunk,updatedAt: action.updatedAt};}return{...chunk};}returnchunk;})MIT