Skip to content

fix(zod-to-json): handle anyOf with more than 2 items#195

Open
marcalexiei wants to merge 1 commit intoturkerdev:mainfrom
marcalexiei:fix-union
Open

fix(zod-to-json): handle anyOf with more than 2 items#195
marcalexiei wants to merge 1 commit intoturkerdev:mainfrom
marcalexiei:fix-union

Conversation

@marcalexiei
Copy link
Copy Markdown
Contributor


This improves anyOf management when overriding zod schemas to produces a OpenAPI schema,

2 elements with one nullable

const VALUE_SCHEMA = z.union([z.null(), z.array(z.string())])

becomes:

{
  "items": {
    "type": "string",
  },
  "nullable": true,
  "type": "array",
},

>= 3 elements with one nullable

const VALUE_SCHEMA = z.union([z.null(), z.array(z.string()), z.literal('any')])

becomes:

{
  "anyOf": [
    {
      "items": {
        "type": "string",
      },
      "type": "array",
    },
    {
      "enum": [
        "any",
      ],
      "type": "string",
    },
  ],
  "nullable": true,
}

Comment thread src/zod-to-json.ts
Comment on lines +52 to +58
Object.assign(
ctx.jsonSchema,
// If length is 2 it means there is only one element besides `null`
notNullableItems[0],
{ nullable: true },
)
delete ctx.jsonSchema.anyOf
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this we ensure that all properties inside the non nullable item are kept (E.g. I have have added a minimum prop via z.number().min(0) in previous test case).

As alternative we could opt to something like this that should produce the same result:

ctx.jsonSchema.nullable = true
ctx.jsonSchema.allOf = notNullableItems
delete ctx.jsonSchema.anyOf

@marcalexiei marcalexiei marked this pull request as ready for review July 19, 2025 10:02
@kibertoad
Copy link
Copy Markdown
Collaborator

@Bram-dc what do you think about these changes, do they look more correct to you?

@marcalexiei
Copy link
Copy Markdown
Contributor Author

@kibertoad these change are limited to the handling of null alongside oneOf.
#197 has a bigger scope (support both OpenAPI 3.0 & 3.1).
Probably the code provided in this PR should be added to the jsonSchemaToOAS_3_0 function there.

@Bram-dc
Copy link
Copy Markdown
Contributor

Bram-dc commented Jul 22, 2025

Could you test if my PR already covers these issues?

@marcalexiei
Copy link
Copy Markdown
Contributor Author

There is no code involving oneOf so I guess it isn't covered.
You can test out yourself using the code I added in test/fastify-swagger.spec.ts.

@Bram-dc
Copy link
Copy Markdown
Contributor

Bram-dc commented Jul 22, 2025

Yes there is, in the recursive function. But I think it is not correct yet.

It now transforms to an array with element:

{
"enum": [null],
"nullable": true
}

@Bram-dc
Copy link
Copy Markdown
Contributor

Bram-dc commented Jul 22, 2025

This is the output of one of your tests:

exports[`null type > should replace \`anyOf\` with \`"allOf": [...], "nullable": true\`  when schema contains only two elements and one is \`"type": "null"\` 1`] = `
{
  "components": {
    "schemas": {},
  },
  "info": {
    "description": "Sample backend service",
    "title": "SampleApi",
    "version": "1.0.0",
  },
  "openapi": "3.0.3",
  "paths": {
    "/": {
      "post": {
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "enum": [
                        null,
                      ],
                      "nullable": true,
                    },
                    {
                      "items": {
                        "type": "string",
                      },
                      "type": "array",
                    },
                  ],
                },
              },
            },
            "description": "Default Response",
          },
        },
      },
    },
  },
  "servers": [],
}

Which is valid OAS 3.0.3 right? Just not optimal. We could write a check to extract the nullable type from the elements. What other fields need this check?

  • allOf
  • anyOf
  • oneOf
  • not
  • then
  • else
  • if
  • contains

@marcalexiei
Copy link
Copy Markdown
Contributor Author

marcalexiei commented Jul 22, 2025

Which is valid OAS 3.0.3 right?

I assume that if pass oas-validator check it is valid for 3.0.3


We could write a check to extract the nullable type from the elements.

Which is basically what it has implemented here.
I assume there should not be a problem adding that code in your recursive function.

@Bram-dc
Copy link
Copy Markdown
Contributor

Bram-dc commented Jul 23, 2025

Would you mind helping me create a PR on top of mine? I am on holiday right now and don't have much time to work on this.

@marcalexiei
Copy link
Copy Markdown
Contributor Author

Yesterday, I published a fork of this plugin under my own scope on npm.
It includes some differences from the upstream version, which are detailed in the README.

I also implemented a fix for OpenAPI 3.0 backward compatibility, based on your PR.

You can check out the commit here:
marcalexiei/fastify-type-provider-zod@89e5b5f

@Bram-dc
Copy link
Copy Markdown
Contributor

Bram-dc commented Aug 4, 2025

Would you mind still creating a PR here?

It is easier to track the changes.

You published 2 repos right?
https://github.com/marcalexiei/fastify-type-provider-zod
https://github.com/marcalexiei/fastify-type-provider-zod-fork

I think the conversion logic is good in the first repo. I prefer converting to 3.0 at the very last step however since that makes the code easier to maintain I think.

the openapi.ts file is similar to my json-to-oas.ts correct?

@marcalexiei
Copy link
Copy Markdown
Contributor Author

marcalexiei commented Aug 5, 2025

Would you mind still creating a PR here?

Last month I’ve already submitted PRs and opened issues on this repo, but unfortunately they’ve gone without feedback.
I’m currently busy and don’t have the bandwidth to open a new PR myself.


You published 2 repos right?

No, I published only https://github.com/marcalexiei/fastify-type-provider-zod into
https://www.npmjs.com/package/@marcalexiei/fastify-type-provider-zod.

https://github.com/marcalexiei/fastify-type-provider-zod-fork is the fork of this repository and still contains the branches of the PRs I opened here.


I think the conversion logic is good in the first repo. I prefer converting to 3.0 at the very last step however since that makes the code easier to maintain I think.

Since OpenAPI is kind of a json schema I preferred to keep that logic inside zod-to-json.


the openapi.ts file is similar to #197 json-to-oas.ts correct?

Yes, as I write in my previous comment:
I also implemented a fix for OpenAPI 3.0 backward compatibility, based on your PR.

@RadovanPelka
Copy link
Copy Markdown

@turkerdev or @kibertoad please guys really needed.

@kibertoad
Copy link
Copy Markdown
Collaborator

@RadovanPelka are you still gettibg issue with the latest version? this has conflicts and needs to be revised in the light of the latest code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Invalid JSON Schema generated when z.null present in union

4 participants