fork of hey-api/openapi-ts because I need some additional things
1type ObjectType<T> =
2 Extract<T, Record<string, any>> extends never
3 ? Record<string, any>
4 : Extract<T, Record<string, any>>;
5
6type NotArray<T> = T extends Array<any> ? never : T;
7type NotFunction<T> = T extends (...args: Array<any>) => any ? never : T;
8type PlainObject<T> = T extends object
9 ? NotFunction<T> extends never
10 ? never
11 : NotArray<T> extends never
12 ? never
13 : T
14 : never;
15
16type MappersType<T> = {
17 boolean: T extends boolean ? (value: boolean) => Partial<ObjectType<T>> : never;
18 function: T extends (...args: Array<any>) => any
19 ? (value: (...args: Array<any>) => any) => Partial<ObjectType<T>>
20 : never;
21 number: T extends number ? (value: number) => Partial<ObjectType<T>> : never;
22 object?: PlainObject<T> extends never
23 ? never
24 : (value: Partial<PlainObject<T>>, defaultValue: PlainObject<T>) => Partial<ObjectType<T>>;
25 string: T extends string ? (value: string) => Partial<ObjectType<T>> : never;
26} extends infer U
27 ? { [K in keyof U as U[K] extends never ? never : K]: U[K] }
28 : never;
29
30type IsObjectOnly<T> = T extends Record<string, any> | undefined
31 ? Extract<T, string | boolean | number | ((...args: Array<any>) => any)> extends never
32 ? true
33 : false
34 : false;
35
36export type ValueToObject = <
37 T extends
38 | undefined
39 | string
40 | boolean
41 | number
42 | ((...args: Array<any>) => any)
43 | Record<string, any>,
44>(
45 args: {
46 defaultValue: ObjectType<T>;
47 value: T;
48 } & (IsObjectOnly<T> extends true
49 ? {
50 mappers?: MappersType<T>;
51 }
52 : {
53 mappers: MappersType<T>;
54 }),
55) => PlainObject<T>;
56
57const isPlainObject = (value: unknown): value is Record<string, any> =>
58 typeof value === 'object' &&
59 value !== null &&
60 !Array.isArray(value) &&
61 typeof value !== 'function';
62
63const mergeResult = <T>(result: ObjectType<T>, mapped: Record<string, any>): ObjectType<T> => {
64 for (const [key, value] of Object.entries(mapped)) {
65 if (value !== undefined && value !== '') {
66 (result as Record<string, any>)[key] = value;
67 }
68 }
69 return result;
70};
71
72export const valueToObject: ValueToObject = ({ defaultValue, mappers, value }) => {
73 let result = { ...defaultValue };
74
75 switch (typeof value) {
76 case 'boolean':
77 if (mappers && 'boolean' in mappers) {
78 const mapper = mappers.boolean as (value: boolean) => Record<string, any>;
79 result = mergeResult(result, mapper(value));
80 }
81 break;
82 case 'function':
83 if (mappers && 'function' in mappers) {
84 const mapper = mappers.function as (
85 value: (...args: Array<any>) => any,
86 ) => Record<string, any>;
87 result = mergeResult(result, mapper(value as (...args: Array<any>) => any));
88 }
89 break;
90 case 'number':
91 if (mappers && 'number' in mappers) {
92 const mapper = mappers.number as (value: number) => Record<string, any>;
93 result = mergeResult(result, mapper(value));
94 }
95 break;
96 case 'string':
97 if (mappers && 'string' in mappers) {
98 const mapper = mappers.string as (value: string) => Record<string, any>;
99 result = mergeResult(result, mapper(value));
100 }
101 break;
102 case 'object':
103 if (isPlainObject(value)) {
104 if (mappers && 'object' in mappers && typeof mappers.object === 'function') {
105 const mapper = mappers.object as (
106 value: Record<string, any>,
107 defaultValue: ObjectType<any>,
108 ) => Partial<ObjectType<any>>;
109 result = mergeResult(result, mapper(value, defaultValue));
110 } else {
111 result = mergeResult(result, value);
112 }
113 }
114 break;
115 }
116
117 return result as any;
118};