Skip to content

Commit b519156

Browse files
committed
feat(utils): ✨ Support oneOf for zod and TypedDict generation
1 parent 9c1162f commit b519156

5 files changed

Lines changed: 77 additions & 0 deletions

File tree

src/utils/json-schema-to-typed-dict.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,26 @@ export function convertToTypedDict(name: string, schema: OpenAPI.SchemaObject |
9292
return `${refName}`;
9393
}
9494

95+
if ('oneOf' in s && Array.isArray(s.oneOf)) {
96+
if (s.oneOf.length === 0) {
97+
typingImports.add('Any');
98+
return 'Any';
99+
}
100+
typingImports.add('Union');
101+
const parts = s.oneOf.map((sub, idx) => toType(sub as any, `${className}Option${idx}`)).join(', ');
102+
return `Union[${parts}]`;
103+
}
104+
105+
if ('anyOf' in s && Array.isArray(s.anyOf)) {
106+
if (s.anyOf.length === 0) {
107+
typingImports.add('Any');
108+
return 'Any';
109+
}
110+
typingImports.add('Union');
111+
const parts = s.anyOf.map((sub, idx) => toType(sub as any, `${className}Option${idx}`)).join(', ');
112+
return `Union[${parts}]`;
113+
}
114+
95115
if (s.enum) {
96116
typingImports.add('Literal');
97117
const values = s.enum.map((v) => JSON.stringify(v)).join(', ');

src/utils/json-schema-to-zod.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ export function convertSchema(schema: OpenAPI.SchemaObject | OpenAPI.ReferenceOb
2828
return expr;
2929
}
3030

31+
if ('oneOf' in s && Array.isArray(s.oneOf)) {
32+
const items = s.oneOf as Array<OpenAPI.SchemaObject | OpenAPI.ReferenceObject>;
33+
if (items.length === 0) {
34+
return 'z.any()';
35+
}
36+
const parts = items.map((sub) => walk(sub)).join(', ');
37+
return `z.union([${parts}])`;
38+
}
39+
40+
if ('anyOf' in s && Array.isArray(s.anyOf)) {
41+
const items = s.anyOf as Array<OpenAPI.SchemaObject | OpenAPI.ReferenceObject>;
42+
if (items.length === 0) {
43+
return 'z.any()';
44+
}
45+
const parts = items.map((sub) => walk(sub)).join(', ');
46+
return `z.union([${parts}])`;
47+
}
48+
3149
if (s.enum) {
3250
const values = s.enum.map((v) => JSON.stringify(v)).join(', ');
3351
return `z.enum([${values}])`;

tests/__snapshots__/json-schema-to-typed-dict.spec.ts.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ class Place(TypedDict, total=False):
3838
name: Required[str]
3939
location: Required[PlaceLocation]"
4040
`;
41+
42+
exports[`generate-python-dict > handles oneOf with refs 1`] = `
43+
"from typing import TypedDict, Union
44+
Message = Union[A, B]"
45+
`;

tests/json-schema-to-typed-dict.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,30 @@ describe('generate-python-dict', () => {
102102
expect(content).toMatchSnapshot();
103103
});
104104

105+
it('handles oneOf with refs', () => {
106+
const doc: OpenAPI.Document = {
107+
openapi: '3.1.0',
108+
info: { title: 't', version: '1' },
109+
paths: {},
110+
components: {
111+
schemas: {
112+
A: { type: 'object', properties: { id: { type: 'string' } } },
113+
B: { type: 'object', properties: { value: { type: 'number' } } },
114+
Message: { oneOf: [{ $ref: '#/components/schemas/A' }, { $ref: '#/components/schemas/B' }] }
115+
}
116+
}
117+
};
118+
const schemas = extractSchemas(doc, null);
119+
const { definition, typingImports } = convertToTypedDict('Message', schemas.Message as OpenAPI.SchemaObject);
120+
const typingLine = `from typing import ${Array.from(typingImports).join(', ')}`;
121+
const content = [typingLine, '', definition, ''].filter(Boolean).join('\n');
122+
const tmp = path.join(__dirname, 'tmp_oneof.py');
123+
fs.writeFileSync(tmp, content);
124+
child_process.execSync(`python3 -m py_compile ${tmp}`);
125+
fs.unlinkSync(tmp);
126+
expect(content).toMatchSnapshot();
127+
});
128+
105129
it('handles inline object properties', () => {
106130
const doc: OpenAPI.Document = {
107131
openapi: '3.1.0',

tests/json-schema-to-zod.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ describe('convertSchema', () => {
5353
expect(imports.has('Base')).toBe(true);
5454
});
5555

56+
it('handles oneOf with refs', () => {
57+
const schema = {
58+
oneOf: [{ $ref: '#/components/schemas/A' }, { $ref: '#/components/schemas/B' }]
59+
} as OpenAPI.SchemaObject;
60+
const { zodString, imports } = convertSchema(schema);
61+
expect(zodString).toBe('z.union([A, B])');
62+
expect(imports.has('A')).toBe(true);
63+
expect(imports.has('B')).toBe(true);
64+
});
65+
5666
it('converts inline object properties', () => {
5767
const schema = {
5868
type: 'object',

0 commit comments

Comments
 (0)