Skip to content

Commit

Permalink
refactor: make boxing type definitions much simpler
Browse files Browse the repository at this point in the history
  • Loading branch information
MrWolfZ committed May 19, 2019
1 parent 95a756b commit e9f504b
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 33 deletions.
19 changes: 18 additions & 1 deletion src/boxing.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { box, isBoxed, unbox } from './boxing';
import { box, Boxed, isBoxed, unbox } from './boxing';

describe(box.name, () => {
it('should box a string value', () => {
Expand Down Expand Up @@ -38,18 +38,35 @@ describe(unbox.name, () => {
expect(unbox(box(value))).toBe(value);
});

it('should unbox a boxed optional string value', () => {
const value = 'A' as string | undefined;
expect(unbox(box(value))).toBe(value);
});

it('should unbox an array value', () => {
const innerValue = 'A';
const value = box([innerValue]);
expect(unbox(value)).toEqual([innerValue]);
});

it('should unbox a readonly array value', () => {
const innerValue = 'A';
const value = box<readonly string[]>([innerValue]);
expect(unbox(value)).toEqual([innerValue]);
});

it('should unbox a value in an array', () => {
const innerValue = 'A';
const value = [box(innerValue)];
expect(unbox(value)).toEqual([innerValue]);
});

it('should unbox a value in a readonly array', () => {
const innerValue = 'A';
const value = [box(innerValue)] as readonly Boxed<string>[];
expect(unbox(value)).toEqual([innerValue]);
});

it('should unbox an object value', () => {
const innerValue = 'A';
const value = box({ inner: innerValue });
Expand Down
38 changes: 7 additions & 31 deletions src/boxing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,21 @@ export interface Boxed<T> {
value: T;
}

export interface UnboxedArray<T> extends Array<T> {
[idx: number]: Unboxed<T>;
}

export type UnboxedObject<T> = {
[prop in keyof T]: Unboxed<T[prop]>;
};

export type Unboxed<T> =
// (ab)use 'symbol' to catch 'any' typing
T extends Boxed<symbol[]> ? any
: T extends Boxed<symbol> ? any
: T extends Boxed<undefined> ? undefined
: T extends Boxed<null> ? null
: T extends Boxed<string> ? string
: T extends Boxed<string | undefined> ? string | undefined
: T extends Boxed<string | null> ? string | null
: T extends Boxed<string | undefined | null> ? string | undefined | null
: T extends Boxed<number> ? number
: T extends Boxed<number | undefined> ? number | undefined
: T extends Boxed<number | null> ? number | null
: T extends Boxed<number | undefined | null> ? number | undefined | null
: T extends Boxed<boolean> ? boolean
: T extends Boxed<boolean | undefined> ? boolean | undefined
: T extends Boxed<boolean | null> ? boolean | null
: T extends Boxed<boolean | undefined | null> ? boolean | undefined | null
: T extends Boxed<(infer U)[]> ? U[]
: T extends Boxed<(infer U)[] | undefined> ? U[] | undefined
: T extends Boxed<(infer U)[] | null> ? U[] | null
: T extends Boxed<(infer U)[] | undefined | null> ? U[] | undefined | null
: T extends Boxed<infer U> ? U
: T extends Boxed<infer U | undefined> ? U | undefined
: T extends Boxed<infer U | null> ? U | null
: T extends Boxed<infer U | undefined | null> ? U | undefined | null
: T extends symbol[] ? any
: T extends symbol ? any
: T extends undefined ? undefined
: T extends null ? null
: T extends string ? string
: T extends number ? number
: T extends boolean ? boolean
: T extends (infer U)[] ? UnboxedArray<U>
: UnboxedObject<T>;

export function isBoxed<T = any>(value: any): value is Boxed<T> {
Expand All @@ -60,16 +33,19 @@ export function box<T>(value: T): Boxed<T> {

export function unbox<T>(value: T): Unboxed<T> {
if (['string', 'boolean', 'number', 'undefined'].indexOf(typeof value) >= 0 || value === null) {
return value as Unboxed<T>;
return value as unknown as Unboxed<T>;
}

if (isBoxed<T>(value)) {
return value.value as Unboxed<T>;
return value.value as unknown as Unboxed<T>;
}

if (Array.isArray(value)) {
return value.map(unbox) as Unboxed<T>;
return (value as any).map(unbox) as Unboxed<T>;
}

return Object.keys(value).reduce((a, k) => Object.assign(a, { [k]: unbox(value[k as keyof T]) }), {} as Unboxed<T>);
return Object.keys(value as any).reduce(
(a, k) => Object.assign(a, { [k]: unbox(value[k as keyof T] as any) }),
{} as any,
) as Unboxed<T>;
}
2 changes: 1 addition & 1 deletion src/ngrx-forms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './actions';
export { box, isBoxed, unbox, Boxed, Unboxed, UnboxedArray, UnboxedObject } from './boxing';
export { box, isBoxed, unbox, Boxed, Unboxed, UnboxedObject } from './boxing';
export {
FormControlValueTypes,
NgrxFormControlId,
Expand Down

0 comments on commit e9f504b

Please sign in to comment.