Skip to content

Instantly share code, notes, and snippets.

@alanthai
Last active May 16, 2017 23:53
Show Gist options
  • Save alanthai/527e15a1bb25a7e707f977315cb53181 to your computer and use it in GitHub Desktop.
Save alanthai/527e15a1bb25a7e707f977315cb53181 to your computer and use it in GitHub Desktop.
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