Skip to content

Commit ea7e171

Browse files
committed
fix: better support optional nested types
1 parent e239a0d commit ea7e171

2 files changed

Lines changed: 78 additions & 29 deletions

File tree

src/caseConvert.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -96,21 +96,23 @@ export type ToCamel<S extends string | number | symbol> = S extends string
9696
: never;
9797

9898
// eslint-disable-next-line @typescript-eslint/ban-types
99-
export type ObjectToCamel<T extends object> = {
100-
// eslint-disable-next-line @typescript-eslint/ban-types
101-
[K in keyof T as ToCamel<K>]: T[K] extends Array<unknown>
102-
? // eslint-disable-next-line @typescript-eslint/ban-types
103-
T[K] extends Array<infer ArrayType>
104-
? // eslint-disable-next-line @typescript-eslint/ban-types
105-
ArrayType extends object
106-
? Array<ObjectToCamel<ArrayType>>
107-
: T[K]
108-
: never
109-
: // eslint-disable-next-line @typescript-eslint/ban-types
110-
T[K] extends object
111-
? ObjectToCamel<T[K]>
112-
: T[K];
113-
};
99+
export type ObjectToCamel<T extends object | undefined> = T extends undefined
100+
? undefined
101+
: {
102+
// eslint-disable-next-line @typescript-eslint/ban-types
103+
[K in keyof T as ToCamel<K>]: T[K] extends Array<unknown>
104+
? // eslint-disable-next-line @typescript-eslint/ban-types
105+
T[K] extends Array<infer ArrayType>
106+
? // eslint-disable-next-line @typescript-eslint/ban-types
107+
ArrayType extends object
108+
? Array<ObjectToCamel<ArrayType>>
109+
: T[K]
110+
: never
111+
: // eslint-disable-next-line @typescript-eslint/ban-types
112+
T[K] extends object | undefined
113+
? ObjectToCamel<T[K]>
114+
: T[K];
115+
};
114116

115117
export type ToSnake<S extends string | number | symbol> = S extends string
116118
? S extends CapitalChars
@@ -153,20 +155,24 @@ export type ToSnake<S extends string | number | symbol> = S extends string
153155
: never;
154156

155157
// eslint-disable-next-line @typescript-eslint/ban-types
156-
export type ObjectToSnake<T extends object> = {
157-
[K in keyof T as ToSnake<K>]: T[K] extends Array<unknown>
158-
? // eslint-disable-next-line @typescript-eslint/ban-types
159-
T[K] extends Array<infer ArrayType>
160-
? // eslint-disable-next-line @typescript-eslint/ban-types
161-
ArrayType extends object
162-
? Array<ObjectToSnake<ArrayType>>
163-
: T[K]
164-
: never
165-
: // eslint-disable-next-line @typescript-eslint/ban-types
166-
T[K] extends object
167-
? ObjectToSnake<T[K]>
168-
: T[K];
169-
};
158+
export type ObjectToSnake<T extends object | undefined> = T extends undefined
159+
? undefined
160+
: {
161+
[K in keyof T as ToSnake<K>]: T[K] extends Array<unknown>
162+
? // eslint-disable-next-line @typescript-eslint/ban-types
163+
T[K] extends Array<infer ArrayType>
164+
? // eslint-disable-next-line @typescript-eslint/ban-types
165+
ArrayType extends object
166+
? Array<ObjectToSnake<ArrayType>>
167+
: T[K]
168+
: never
169+
: // eslint-disable-next-line @typescript-eslint/ban-types
170+
T[K] extends object | undefined
171+
? ObjectToSnake<T[K]>
172+
: // Exclude<T[K], undefined> extends object
173+
// ? ObjectToSnake<Exclude<T[K], undefined>>
174+
T[K];
175+
};
170176

171177
type CapitalLetters =
172178
| 'A'

test/caseConvert.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ToCamel,
66
ToSnake,
77
toCamel,
8+
ObjectToSnake,
89
} from '../src/caseConvert';
910

1011
describe('Property name converter', () => {
@@ -19,6 +20,9 @@ describe('Property name converter', () => {
1920
an_object: {
2021
a_1: 'a1',
2122
a_2: 'a2',
23+
a_3: {
24+
b_4: 'b4',
25+
},
2226
},
2327
});
2428

@@ -33,6 +37,7 @@ describe('Property name converter', () => {
3337
expect(testToCamel.anArrayOfObjects[0].aC).toEqual('ac');
3438
expect(testToCamel.anObject.a1).toEqual('a1');
3539
expect(testToCamel.anObject.a2).toEqual('a2');
40+
expect(testToCamel.anObject.a3.b4).toEqual('b4');
3641
});
3742

3843
it('converts to snake_case', () => {
@@ -46,6 +51,9 @@ describe('Property name converter', () => {
4651
anObject: {
4752
A1: 'a_1',
4853
A2: 'a_2',
54+
A3: {
55+
B4: 'b_4',
56+
},
4957
},
5058
});
5159

@@ -60,6 +68,7 @@ describe('Property name converter', () => {
6068
expect(testToSnake.an_array_of_objects[0].a_c).toEqual('ac');
6169
expect(testToSnake.an_object.a1).toEqual('a_1');
6270
expect(testToSnake.an_object.a2).toEqual('a_2');
71+
expect(testToSnake.an_object.a3.b4).toEqual('b_4');
6372
});
6473
});
6574

@@ -181,3 +190,37 @@ type T22 = ToSnake<'abc25D50'>;
181190
const _s22: AssertEqual<T22, 'abc_25_d50'> = true;
182191
type T23 = ToSnake<'abc25A50'>;
183192
const _s23: AssertEqual<T23, 'abc_25_a50'> = true;
193+
194+
interface I24 {
195+
optionalObject?: {
196+
aProp: string;
197+
bProp:
198+
| {
199+
cProp: string;
200+
}
201+
| undefined;
202+
};
203+
}
204+
205+
interface I242 {
206+
optional_object?: {
207+
a_prop: string;
208+
b_prop:
209+
| {
210+
c_prop: string;
211+
}
212+
| undefined;
213+
};
214+
}
215+
216+
const _c24: I24 = {
217+
optionalObject: {
218+
aProp: 'a',
219+
bProp: {
220+
cProp: 'c',
221+
},
222+
},
223+
};
224+
const _c242: I242 = objectToSnake(_c24);
225+
226+
const _s24: AssertEqual<I242, ObjectToSnake<I24>> = true;

0 commit comments

Comments
 (0)