From 478a6da4abbe95b150fa1c14dd925f9b0c2b598d Mon Sep 17 00:00:00 2001 From: Kamaal Farah Date: Mon, 20 May 2024 14:12:16 +0200 Subject: [PATCH] Adding unflatten to objects --- src/index.ts | 1 + src/objects/flatten.ts | 6 ++++-- src/objects/index.ts | 1 + src/objects/unflatten.test.ts | 25 +++++++++++++++++++++++++ src/objects/unflatten.ts | 33 +++++++++++++++++++++++++++++++++ src/types/arrays.ts | 1 + src/types/index.ts | 2 ++ src/types/strings.ts | 7 +++++++ 8 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/objects/unflatten.test.ts create mode 100644 src/objects/unflatten.ts create mode 100644 src/types/arrays.ts create mode 100644 src/types/index.ts create mode 100644 src/types/strings.ts diff --git a/src/index.ts b/src/index.ts index d000e93..bdd89cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,3 +3,4 @@ export * as maths from './maths'; export * as strings from './strings'; export * as arrays from './arrays'; export * as shell from './shell'; +export * as types from './types'; diff --git a/src/objects/flatten.ts b/src/objects/flatten.ts index 3a4b230..871d907 100644 --- a/src/objects/flatten.ts +++ b/src/objects/flatten.ts @@ -1,4 +1,4 @@ -function _flatten( +function _flatten>( obj: Record, recursiveContext?: { parentKey: string; result: Result } ): Result { @@ -17,7 +17,9 @@ function _flatten( }, initialResult); } -function flatten(obj: Record): Result { +function flatten>( + obj: Record +): Result { return _flatten(obj); } diff --git a/src/objects/index.ts b/src/objects/index.ts index e172786..dc2ebe3 100644 --- a/src/objects/index.ts +++ b/src/objects/index.ts @@ -1,3 +1,4 @@ export * from './omit'; export * from './omitBy'; export * from './flatten'; +export * from './unflatten'; diff --git a/src/objects/unflatten.test.ts b/src/objects/unflatten.test.ts new file mode 100644 index 0000000..7e0c81e --- /dev/null +++ b/src/objects/unflatten.test.ts @@ -0,0 +1,25 @@ +import unflatten from './unflatten'; + +describe('unflatten', () => { + it('unflatten the given object', () => { + const flatObject = { + 'hello.yes': 'true', + flat: 'yes', + 'nested.array': [1, 2], + 'furthernested.yes.no': false, + }; + + const result = unflatten(flatObject, '.'); + + expect(result).toEqual({ + hello: { + yes: 'true', + }, + flat: 'yes', + nested: { + array: [1, 2], + }, + furthernested: { yes: { no: false } }, + }); + }); +}); diff --git a/src/objects/unflatten.ts b/src/objects/unflatten.ts new file mode 100644 index 0000000..c8a5e71 --- /dev/null +++ b/src/objects/unflatten.ts @@ -0,0 +1,33 @@ +import type { NonEmptyArray } from '../types/arrays'; +import type { Character } from '../types/strings'; + +function setNestedValue( + obj: Result, + keys: NonEmptyArray, + value: unknown +): Result { + const [first, ...rest] = keys; + if (rest.length === 0) return { ...obj, [first]: value }; + + const nestedObject = (obj as Record)[first] ?? {}; + + return { + ...obj, + [first]: setNestedValue(nestedObject, rest as NonEmptyArray, value), + }; +} + +function unflatten( + data: object, + delimiter: Character +): Result { + const initialResult = {} as unknown as Result; + + return Object.entries(data).reduce((acc, [key, value]) => { + const keys = key.split(delimiter) as NonEmptyArray; + + return setNestedValue(acc, keys, value); + }, initialResult); +} + +export default unflatten; diff --git a/src/types/arrays.ts b/src/types/arrays.ts new file mode 100644 index 0000000..24ff3df --- /dev/null +++ b/src/types/arrays.ts @@ -0,0 +1 @@ +export type NonEmptyArray = [TargetArray, ...TargetArray[]]; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..6b20e56 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,2 @@ +export * from './strings'; +export * from './arrays'; diff --git a/src/types/strings.ts b/src/types/strings.ts new file mode 100644 index 0000000..3d43de7 --- /dev/null +++ b/src/types/strings.ts @@ -0,0 +1,7 @@ +type OnlyFirstCharacter = + TargetString extends `${infer $TFirstChar}${string}` ? $TFirstChar : string; + +export type Character = + TargetString extends TargetString & OnlyFirstCharacter + ? TargetString & OnlyFirstCharacter + : string & { length: 1 };