Skip to content

Commit 409289e

Browse files
committed
Default values are now applied properly for single-type unions, even a mix of integers and numbers.
1 parent cfe7d88 commit 409289e

File tree

3 files changed

+50
-10
lines changed

3 files changed

+50
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ Headlines: Added, Changed, Deprecated, Removed, Fixed, Security
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Fixed
11+
12+
- Default values are now applied properly for single-type unions, even a mix of integers and numbers.
13+
814
## [2.1.0] - 2024-02-12
915

1016
### Fixed

src/lib/jsonSchema/schemaDefaults.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,21 @@ function _defaultValues(schema: JSONSchema, isOptional: boolean, path: string[])
5353
}
5454
}
5555

56+
let _multiType: Set<string>;
57+
const isMultiTypeUnion = () => {
58+
if (!info.union || info.union.length < 2) return false;
59+
if (info.union.some((i) => i.enum)) return true;
60+
console.log(info.union, info.types, _multiType); //debug
61+
if (!_multiType) {
62+
_multiType = new Set(
63+
info.types.map((i) => {
64+
return ['integer', 'unix-time'].includes(i) ? 'number' : i;
65+
})
66+
);
67+
}
68+
return _multiType.size > 1;
69+
};
70+
5671
// Check unions first, so default values can take precedence over nullable and optional
5772
if (!objectDefaults && info.union) {
5873
const singleDefault = info.union.filter(
@@ -65,20 +80,17 @@ function _defaultValues(schema: JSONSchema, isOptional: boolean, path: string[])
6580
'Only one default value can exist in a union, or set a default value for the whole union.',
6681
path
6782
);
68-
} else if (info.union.length > 1) {
69-
throw new SchemaError(
70-
'Unions must have a default value, or exactly one of the union types must have.',
71-
path
72-
);
7383
} else {
7484
// Null takes priority over undefined
7585
if (info.isNullable) return null;
7686
if (info.isOptional) return undefined;
7787

78-
throw new SchemaError(
79-
'Unions must have a default value, or exactly one of the union types must have.',
80-
path
81-
);
88+
if (isMultiTypeUnion()) {
89+
throw new SchemaError(
90+
'Multi-type unions must have a default value, or exactly one of the union types must have.',
91+
path
92+
);
93+
}
8294
}
8395
}
8496

@@ -114,7 +126,7 @@ function _defaultValues(schema: JSONSchema, isOptional: boolean, path: string[])
114126
}
115127

116128
// Basic type
117-
if (info.types.length > 1) {
129+
if (isMultiTypeUnion()) {
118130
throw new SchemaError('Default values cannot have more than one type.', path);
119131
} else if (info.types.length == 0) {
120132
//console.warn('No type or format for property:', path); //debug

src/tests/superValidate.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,28 @@ describe('Zod', () => {
374374
});
375375
});
376376

377+
it('should not require a default value for single type unions', () => {
378+
const schema = z.object({
379+
letter: z.union([z.literal('a'), z.literal('b')]),
380+
num: z.union([z.number().int().negative(), z.number()])
381+
});
382+
383+
const adapter = zod(schema);
384+
expect(adapter.defaults).toEqual({ letter: 'a', num: 0 });
385+
expect(adapter.constraints.letter?.required).toBe(true);
386+
expect(adapter.constraints.num?.required).toBe(true);
387+
});
388+
389+
it('should not require a default value for enums', () => {
390+
const schema = z.object({
391+
letter: z.enum(['a', 'b', 'c'])
392+
});
393+
394+
const adapter = zod(schema);
395+
expect(adapter.defaults.letter).toBe('a');
396+
expect(adapter.constraints.letter?.required).toBe(true);
397+
});
398+
377399
schemaTest(zod(schema));
378400
});
379401

0 commit comments

Comments
 (0)