Skip to content

Commit b595d34

Browse files
fix(schema): camel to snake case in schema from and to IDL (#884)
1 parent 18c0c0b commit b595d34

File tree

2 files changed

+94
-6
lines changed

2 files changed

+94
-6
lines changed

packages/schema/src/tests/utils/idl.spec.ts

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ describe('idl', () => {
5252
expect(
5353
schemaToIdl({
5454
schema: Schema,
55-
value: {username: 'David', status: {type: 'active', owner: 'abc'}}
55+
value: {username: 'Hello', status: {type: 'active', owner: 'abc'}}
5656
})
5757
).toEqual({
58-
username: 'David',
58+
username: 'Hello',
5959
status: {active: {owner: 'abc'}}
6060
});
6161
});
@@ -132,13 +132,94 @@ describe('idl', () => {
132132
expect(
133133
schemaFromIdl({
134134
schema: Schema,
135-
value: {username: 'David', status: {active: {owner: 'abc'}}}
135+
value: {username: 'Hello', status: {active: {owner: 'abc'}}}
136136
})
137137
).toEqual({
138-
username: 'David',
138+
username: 'Hello',
139139
status: {type: 'active', owner: 'abc'}
140140
});
141141
});
142142
});
143143
});
144+
145+
describe('camel case', () => {
146+
describe('object with camelCase fields', () => {
147+
const Schema = z.object({firstName: z.string(), lastName: z.string()});
148+
149+
it('converts camelCase keys to snake_case', () => {
150+
expect(
151+
schemaToIdl({schema: Schema, value: {firstName: 'Hello', lastName: 'World'}})
152+
).toEqual({
153+
first_name: 'Hello',
154+
last_name: 'World'
155+
});
156+
});
157+
});
158+
159+
describe('object with camelCase fields', () => {
160+
const Schema = z.object({firstName: z.string(), lastName: z.string()});
161+
162+
it('converts snake_case keys back to camelCase', () => {
163+
expect(
164+
schemaFromIdl({schema: Schema, value: {first_name: 'Hello', last_name: 'World'}})
165+
).toEqual({
166+
firstName: 'Hello',
167+
lastName: 'World'
168+
});
169+
});
170+
});
171+
172+
describe('nested object with camelCase fields', () => {
173+
const Schema = z.object({
174+
userId: z.string(),
175+
userProfile: z.object({displayName: z.string()})
176+
});
177+
178+
it('converts nested camelCase keys to snake_case', () => {
179+
expect(
180+
schemaToIdl({
181+
schema: Schema,
182+
value: {userId: 'abc', userProfile: {displayName: 'Hello'}}
183+
})
184+
).toEqual({
185+
user_id: 'abc',
186+
user_profile: {display_name: 'Hello'}
187+
});
188+
});
189+
190+
it('converts nested snake_case keys back to camelCase', () => {
191+
expect(
192+
schemaFromIdl({
193+
schema: Schema,
194+
value: {user_id: 'abc', user_profile: {display_name: 'Hello'}}
195+
})
196+
).toEqual({
197+
userId: 'abc',
198+
userProfile: {displayName: 'Hello'}
199+
});
200+
});
201+
});
202+
});
203+
204+
describe('snake_case fields', () => {
205+
const Schema = z.object({first_name: z.string(), last_name: z.string()});
206+
207+
it('keeps snake_case keys unchanged in schemaToIdl', () => {
208+
expect(
209+
schemaToIdl({schema: Schema, value: {first_name: 'Hello', last_name: 'World'}})
210+
).toEqual({
211+
first_name: 'Hello',
212+
last_name: 'World'
213+
});
214+
});
215+
216+
it('keeps snake_case keys unchanged in schemaFromIdl', () => {
217+
expect(
218+
schemaFromIdl({schema: Schema, value: {first_name: 'Hello', last_name: 'World'}})
219+
).toEqual({
220+
first_name: 'Hello',
221+
last_name: 'World'
222+
});
223+
});
224+
});
144225
});

packages/schema/src/utils/idl.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export interface IdlParams {
55
value: unknown;
66
}
77

8+
// Duplicate utils to avoid to reference library utils in schema
9+
const convertCamelToSnake = (str: string): string =>
10+
str.replace(/([a-zA-Z])(?=[A-Z])/g, '$1_').toLowerCase();
11+
812
/**
913
* Recursively converts a JavaScript value to its Candid IDL representation,
1014
* guided by a Zod schema.
@@ -53,7 +57,7 @@ export const schemaToIdl = ({schema, value}: IdlParams): unknown => {
5357
if (schema instanceof z.ZodObject) {
5458
return Object.fromEntries(
5559
Object.entries(schema._zod.def.shape).map(([k, t]) => [
56-
k,
60+
convertCamelToSnake(k),
5761
schemaToIdl({schema: t as z.core.$ZodType, value: (value as Record<string, unknown>)[k]})
5862
])
5963
);
@@ -129,7 +133,10 @@ export const schemaFromIdl = ({schema, value}: IdlParams): unknown => {
129133
return Object.fromEntries(
130134
Object.entries(schema._zod.def.shape).map(([k, t]) => [
131135
k,
132-
schemaFromIdl({schema: t as z.core.$ZodType, value: (value as Record<string, unknown>)[k]})
136+
schemaFromIdl({
137+
schema: t as z.core.$ZodType,
138+
value: (value as Record<string, unknown>)[convertCamelToSnake(k)]
139+
})
133140
])
134141
);
135142
}

0 commit comments

Comments
 (0)