Last active
May 16, 2017 23:53
-
-
Save alanthai/527e15a1bb25a7e707f977315cb53181 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { assocPath, flip, path, init, last, toPairs } from 'ramda'; | |
function isObject(value: any): boolean { | |
return Object.prototype.toString.call(value) === '[object Object]'; | |
} | |
function isFunction(value: any): boolean { | |
return Object.prototype.toString.call(value) === '[object Function]'; | |
} | |
// { a: { b: 'hello', c: 'world' } } | |
// => [['a', 'b', 'hello'], ['a', 'c', 'world']] | |
function getPathsAndValues(rootObj: any): any[] { | |
const paths: string[][] = []; | |
(function getPathAndValue(obj: any, path: string[] = []): void { | |
toPairs(obj) | |
.forEach(([key, value]: [string, any]) => { | |
if (isObject(value)) { | |
getPathAndValue(value, path.concat(key)); | |
} else { | |
paths.push(path.concat([key, value])); | |
} | |
}); | |
})(rootObj); | |
return paths; | |
} | |
/** | |
* Example: | |
* const object = { A: 1, B: 2, C: { D: 3 }, E: 4, f: 5 } | |
* const mapping = { a: 'A', b: { c: 'B' }, d: 'C.D', e: (obj) => obj.E, f: '' } | |
* remap(mapping, object); | |
* => { a: 1, b: { c: 2 }, d: 3, e: 4, f: 5 } | |
*/ | |
// assumes no arrays | |
export function remap(mapping: any, obj: any, context?: any): any { | |
const pathsAndValues = getPathsAndValues(mapping); | |
const objGet = flip(path)(obj); | |
return pathsAndValues | |
.reduce( | |
(newObj, destAndOrigin) => { | |
const destinationPath = init<string>(destAndOrigin); | |
const originPath = last(destAndOrigin); | |
const value = isFunction(originPath) | |
? (originPath as Function)(obj, context) | |
: originPath !== '' | |
? objGet((originPath as string).split('.')) | |
: objGet(destinationPath); | |
return assocPath(destinationPath, value, newObj); | |
}, | |
{}, | |
); | |
} | |
/** | |
* Works with arrays. Can only map arrays to other arrays of same length | |
* Meant to be used in conjunction with remap | |
* Example: | |
* const object = { A: [{ B: 1 }, { B: 2 }], C: 3 } | |
* const mapping = { a: remapArray('A', { b: 'B' }), c: 'C' } | |
* remap(mapping, object); | |
* => { a: [{ b: 1 }, { b: 2 }], c: 3 } | |
*/ | |
export function remapArray(strPath: string, mapping: any): any { | |
const arrayPath = strPath.split('.'); | |
return (obj: any) => { | |
const array = path(arrayPath, obj) as any[]; | |
if (!Array.isArray(array)) { | |
throw Error('remapArray: path does not lead to array'); | |
} | |
return array.map((item, index) => remap(mapping, item, index)); | |
}; | |
} | |
// --- testing --- | |
import { remap, remapArray } from './remap'; | |
describe('remap', () => { | |
it('should remap shallow objects', () => { | |
const mapping = { a: 'b' }; | |
const object = { b: 1 }; | |
const expected = { a: 1 }; | |
const actual = remap(mapping, object); | |
expect(actual).toEqual(expected); | |
}); | |
it('should remap from deep objects', () => { | |
const mapping = { a: 'b.c' }; | |
const object = { b: { c: 1 } }; | |
const expected = { a: 1 }; | |
const actual = remap(mapping, object); | |
expect(actual).toEqual(expected); | |
}); | |
it('should remap to deep objects', () => { | |
const mapping = { a: { b: 'c' } }; | |
const object = { c: 1 }; | |
const expected = { a: { b: 1 } }; | |
const actual = remap(mapping, object); | |
expect(actual).toEqual(expected); | |
}); | |
it('should allow function mappings', () => { | |
const mapping = { a: (obj) => obj.b }; | |
const object = { b: 1 }; | |
const expected = { a: 1 }; | |
const actual = remap(mapping, object); | |
expect(actual).toEqual(expected); | |
}); | |
it('should use same path if mapping is empty string', () => { | |
const mapping = { a: '' }; | |
const object = { a: 1, b: 2 }; | |
const expected = { a: 1 }; | |
const actual = remap(mapping, object); | |
expect(actual).toEqual(expected); | |
}); | |
}); | |
describe('remapArray()', () => { | |
it('should remap arrays given object path', () => { | |
const object = { a: [{ b: 1 }, { b: 2 }] }; | |
const expected = [{ c: 1 }, { c: 2 }]; | |
const arrayMapping = { c: 'b' }; | |
const actual = remapArray('a', arrayMapping)(object); | |
expect(actual).toEqual(expected); | |
}); | |
it('should allow deep mapping', () => { | |
const object = { z: { a: [{ b: 1 }, { b: 2 }] } }; | |
const expected = [{ c: 1 }, { c: 2 }]; | |
const arrayMapping = { c: 'b' }; | |
const actual = remapArray('z.a', arrayMapping)(object); | |
expect(actual).toEqual(expected); | |
}); | |
it('should allow functions that pass object and index', () => { | |
const object = { a: [{ b: 1 }, { b: 2 }] }; | |
const expected = [{ c: '1-0' }, { c: '2-1' }]; | |
const arrayMapping = { c: (item, index) => `${item.b}-${index}` }; | |
const actual = remapArray('a', arrayMapping)(object); | |
expect(actual).toEqual(expected); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment