-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add object utilities flattenArray, flattenObject, unflattenObject (#426)
* Working * Self-CR * Format and remove no-longer-true comment * Switch to Github Actions * Fix workflows * Prettier * Self-CR
- Loading branch information
1 parent
0aee881
commit f6fd7c4
Showing
5 changed files
with
147 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
name: CI | ||
on: [push] | ||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Begin CI... | ||
uses: actions/checkout@v2 | ||
|
||
- name: Use Node 12 | ||
uses: actions/setup-node@v1 | ||
with: | ||
node-version: 12.x | ||
|
||
- name: Use cached node_modules | ||
uses: actions/cache@v1 | ||
with: | ||
path: node_modules | ||
key: nodeModules-${{ hashFiles('yarn.lock') }} | ||
restore-keys: | | ||
nodeModules- | ||
- name: Install dependencies | ||
run: yarn install --frozen-lockfile | ||
env: | ||
CI: true | ||
|
||
- name: Lint | ||
run: yarn lint | ||
env: | ||
CI: true | ||
|
||
- name: Test | ||
run: yarn test --ci --coverage --maxWorkers=2 --colors | ||
env: | ||
CI: true | ||
|
||
- name: Coverage | ||
uses: coverallsapp/github-action@master | ||
with: | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
- name: Build | ||
run: yarn build | ||
env: | ||
CI: true |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ _book | |
.cache | ||
source.html | ||
*.log | ||
build | ||
|
||
# OS X | ||
.DS_Store |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { flatten as flattenArray, isArray, isPlainObject, set } from 'lodash'; | ||
|
||
// Complex types sourced from | ||
// https://flut1.medium.com/deep-flatten-typescript-types-with-finite-recursion-cb79233d93ca | ||
|
||
type NonObjectKeysOf<T> = { | ||
[K in keyof T]: T[K] extends Array<any> ? K : T[K] extends object ? never : K; | ||
}[keyof T]; | ||
|
||
type ValuesOf<T> = T[keyof T]; | ||
|
||
type ObjectValuesOf<T> = Exclude<Extract<ValuesOf<T>, object>, Array<any>>; | ||
|
||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; | ||
|
||
type Flatten<T> = Pick<T, NonObjectKeysOf<T>> & UnionToIntersection<ObjectValuesOf<T>>; | ||
|
||
function mergeObjects<A extends object, B extends object>(objectA: A, objectB: B): A & B { | ||
return { ...objectA, ...objectB }; | ||
} | ||
|
||
const _hasUnflattenedValues = (value: unknown): boolean => { | ||
return (isArray(value) || isPlainObject(value)) && !!Object.keys(value).length; | ||
}; | ||
|
||
function _flattenObject<T>(input: T, parentKey: string): Flatten<T> { | ||
const _getFlatKey = (key: string) => { | ||
if (isArray(input)) { | ||
return `${parentKey}[${key}]`; | ||
} | ||
|
||
if (parentKey) { | ||
return `${parentKey}.${key}`; | ||
} | ||
|
||
return key; | ||
}; | ||
|
||
return Object.entries(input).reduce((output: Flatten<T>, [key, value]) => { | ||
const flatKey = _getFlatKey(key); | ||
|
||
if (_hasUnflattenedValues(value)) { | ||
const flatValues = _flattenObject(value, flatKey); | ||
return mergeObjects(output, flatValues); | ||
} | ||
|
||
return mergeObjects(output, { [flatKey]: value }); | ||
}, {}); | ||
} | ||
|
||
function flattenObject<T extends object>(input: T): Flatten<T> { | ||
return _flattenObject(input, ''); | ||
} | ||
|
||
function unflattenObject(object: Object) { | ||
return Object.entries(flattenObject(object)).reduce((objOut, [key, value]) => set(objOut, key, value), {}); | ||
} | ||
|
||
export { flattenArray, flattenObject, unflattenObject }; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { flattenObject, unflattenObject } from '../src/objects'; | ||
|
||
const COMPLETELY_FLATTENED_OBJECTS: Array<[object, object]> = [ | ||
[{ x: 'y' }, { x: 'y' }], | ||
[{ 'x[0]': 'y' }, { x: ['y'] }], | ||
[{ 'x[0]': 'y', 'x[1]': 'z' }, { x: ['y', 'z'] }], | ||
[{ 'x[0].a': 'b' }, { x: [{ a: 'b' }] }], | ||
[{ 'x.y[0]': 'z' }, { x: { y: ['z'] } }], | ||
[ | ||
{ 'a[0].b[0].c': 'x', 'a[0].b[1].c': 'y', 'a[1].d': 'z' }, | ||
{ | ||
a: [{ b: [{ c: 'x' }, { c: 'y' }] }, { d: 'z' }], | ||
}, | ||
], | ||
], | ||
PARTIALLY_FLATTENED_OBJECTS: Array<[object, object]> = [ | ||
[{ 'x[0]': { a: 'b' } }, { x: [{ a: 'b' }] }], | ||
[{ x: { 'y[0]': 'z' } }, { x: { y: ['z'] } }], | ||
[ | ||
{ 'a[0]': { 'b[0].c': 'x' }, 'a[0].b[1]': { c: 'y' }, 'a[1].d': 'z' }, | ||
{ a: [{ b: [{ c: 'x' }, { c: 'y' }] }, { d: 'z' }] }, | ||
], | ||
]; | ||
|
||
describe('flattenObject', () => { | ||
it(`Correctly flattens objects`, () => { | ||
COMPLETELY_FLATTENED_OBJECTS.forEach(([flat, deep]) => { | ||
expect(flattenObject(deep)).toStrictEqual(flat); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('unflattenObject', () => { | ||
it(`Correctly unflattens objects`, () => { | ||
[...COMPLETELY_FLATTENED_OBJECTS, ...PARTIALLY_FLATTENED_OBJECTS].forEach(([flat, deep]) => { | ||
expect(unflattenObject(flat)).toStrictEqual(deep); | ||
}); | ||
}); | ||
}); |