Skip to content

Commit 0675347

Browse files
committed
Merge branch 'additionalproperties'
2 parents 393575a + 7f948c0 commit 0675347

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

src/lib/jsonSchema/schemaDefaults.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,20 @@ function _defaultTypes(schema: JSONSchema, isOptional: boolean, path: string[])
260260
}
261261
}
262262

263+
// Check if a Record type is used for additionalProperties
264+
if (info.additionalProperties && info.types.includes('object')) {
265+
const additionalInfo = schemaInfo(info.additionalProperties, info.isOptional, path);
266+
if (additionalInfo.properties && additionalInfo.types.includes('object')) {
267+
for (const [key] of Object.entries(additionalInfo.properties)) {
268+
output[key] = _defaultTypes(
269+
additionalInfo.properties[key],
270+
!additionalInfo.required?.includes(key),
271+
[...path, key]
272+
);
273+
}
274+
}
275+
}
276+
263277
if (info.isNullable && !output.__types.includes('null')) {
264278
output.__types.push('null');
265279
}

src/lib/jsonSchema/schemaInfo.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type SchemaInfo = {
2222
union?: JSONSchema7[];
2323
array?: JSONSchema7[];
2424
properties?: { [key: string]: JSONSchema7 };
25+
additionalProperties?: { [key: string]: JSONSchema7 };
2526
required?: string[];
2627
};
2728

@@ -57,6 +58,17 @@ export function schemaInfo(
5758
) as JSONSchema7[])
5859
: undefined;
5960

61+
const additionalProperties =
62+
schema.additionalProperties &&
63+
typeof schema.additionalProperties === 'object' &&
64+
types.includes('object')
65+
? (Object.fromEntries(
66+
Object.entries(schema.additionalProperties).filter(
67+
([, value]) => typeof value !== 'boolean'
68+
)
69+
) as { [key: string]: JSONSchema7 })
70+
: undefined;
71+
6072
const properties =
6173
schema.properties && types.includes('object')
6274
? (Object.fromEntries(
@@ -74,6 +86,7 @@ export function schemaInfo(
7486
union: union?.length ? union : undefined,
7587
array,
7688
properties,
89+
additionalProperties,
7790
required: schema.required
7891
};
7992
}

src/tests/superValidate.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,65 @@ describe('Zod', () => {
656656
expect(sameAdapter).toBe(adapter);
657657
});
658658

659+
describe('with z.record', () => {
660+
it('should work with additionalProperties for records', async () => {
661+
/*
662+
{
663+
type: 'object',
664+
properties: {
665+
id: { type: 'string' },
666+
options: {
667+
type: 'object',
668+
additionalProperties: {
669+
type: 'object',
670+
properties: { label: { type: 'string' } },
671+
required: [ 'label' ],
672+
additionalProperties: false
673+
}
674+
}
675+
},
676+
required: [ 'id', 'options' ],
677+
additionalProperties: false,
678+
'$schema': 'http://json-schema.org/draft-07/schema#'
679+
}
680+
*/
681+
const schema = z.object({
682+
id: z.string(),
683+
options: z.record(
684+
z.string(),
685+
z.object({
686+
label: z.string().refine((value) => value.length > 0, {
687+
message: 'Label is required'
688+
})
689+
})
690+
)
691+
});
692+
693+
const row = {
694+
id: '1',
695+
options: {
696+
'1': {
697+
label: 'Option 1'
698+
},
699+
'2': {
700+
label: ''
701+
}
702+
}
703+
};
704+
705+
const adapter = zod(schema);
706+
const form = await superValidate(row, adapter);
707+
708+
assert(!form.valid);
709+
710+
expect(form.errors).toStrictEqual({
711+
options: { '2': { label: ['Label is required'] } }
712+
});
713+
714+
expect(form.data).toEqual(row);
715+
});
716+
});
717+
659718
schemaTest(zod(schema));
660719
});
661720

0 commit comments

Comments
 (0)