-
-
Notifications
You must be signed in to change notification settings - Fork 29
Description
Hi, thanks for your work on this package! I encountered a bug in openapi-generator today and traced it back to the following behaviour of zod-openapi. Not a bug here, this issue is more for awareness.
Problem
When converting Zod schemas with .nullable() to OpenAPI 3.1.0, zod-openapi produces:
{"anyOf": [{"type": "string"}, {"type": "null"}]}This is valid OpenAPI 3.1.0, but it triggers a bug in openapi-generator (typescript-fetch generator) where path parameters in unrelated operations get incorrectly typed as string | null instead of string.
Proposed Solution
Use the equivalent type array syntax instead:
{"type": ["string", "null"]}Both are valid OpenAPI 3.1.0 for expressing nullable types, but the type array syntax:
- Is more concise
- Doesn't trigger the openapi-generator bug
- Is arguably more idiomatic for simple nullable types
Example
Zod schema:
const schema = z.object({
value: z.string().nullable()
});Current output:
{
"type": "object",
"properties": {
"value": {
"anyOf": [{"type": "string"}, {"type": "null"}]
}
}
}Proposed output:
{
"type": "object",
"properties": {
"value": {
"type": ["string", "null"]
}
}
}Context
The openapi-generator bug has been reported at: OpenAPITools/openapi-generator#22427
The bug causes state corruption where anyOf containing {type: "null"} anywhere in components.schemas contaminates path parameter type inference for completely unrelated operations. The nullable schema doesn't even need to be referenced by the affected paths.
Workaround
Users can post-process the generated OpenAPI spec to convert anyOf nullable patterns:
function fixNullableAnyOf(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(fixNullableAnyOf);
if (obj.anyOf && Array.isArray(obj.anyOf) && obj.anyOf.length === 2) {
const types = obj.anyOf.map(item => item.type).filter(Boolean);
if (types.length === 2 && types.includes('null')) {
const nonNullType = types.find(t => t !== 'null');
if (nonNullType && typeof nonNullType === 'string') {
const { anyOf, ...rest } = obj;
return { ...fixNullableAnyOf(rest), type: [nonNullType, 'null'] };
}
}
}
const result = {};
for (const [key, value] of Object.entries(obj)) {
result[key] = fixNullableAnyOf(value);
}
return result;
}Alternatives Considered
- Configuration option - Add an option like
nullableStyle: 'typeArray' | 'anyOf'to let users choose - Keep current behavior - Wait for openapi-generator to fix the bug
Additional Context
- OpenAPI 3.1.0 spec reference: Both
anyOfand type arrays are valid for nullable types - This affects users generating TypeScript clients with openapi-generator
- The
anyOfsyntax may still be appropriate for more complex unions (e.g.,z.union([z.string(), z.number()]).nullable())