astahmer / openapi-zod-client

Generate a zodios (typescript http client with zod validation) from an OpenAPI spec (json/yaml)
openapi-zod-client.vercel.app
817 stars 89 forks source link

z.discriminatedUnion does not support lazy zod objects #303

Open yohoji opened 3 months ago

yohoji commented 3 months ago

Is your issue related to zod or zodios ? Yes, but it can be solved here.

Describe the bug z.discriminatedUnion does not support lazy zod objects. We need to ensure that zod can extract the discriminator. so my proposed solution is to apply z.lazy() only to the properties that require lazy evaluation, rather than wrapping the entire object.

Minimal reproduction

{
    "openapi": "3.0.1",
    "info": {
        "title": "",
        "version": "v1"
    },
    "paths": {
        "/Datapoint/GetById": {
            "get": {
                "responses": {
                    "200": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/Datapoint"
                                        }
                                    ],
                                    "discriminator": {
                                        "propertyName": "_t"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "FileAsset": {
                "title": "FileAsset",
                "required": ["_t", "fileName"],
                "type": "object",
                "properties": {
                    "_t": {
                        "enum": ["FileAsset"],
                        "type": "string"
                    },
                    "fileName": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            },
            "MultiAsset": {
                "title": "MultiAsset",
                "required": ["_t", "assets"],
                "type": "object",
                "properties": {
                    "_t": {
                        "enum": ["MultiAsset"],
                        "type": "string"
                    },
                    "assets": {
                        "type": "array",
                        "items": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/FileAsset"
                                },
                                {
                                    "$ref": "#/components/schemas/MultiAsset"
                                }
                            ],
                            "discriminator": {
                                "propertyName": "_t"
                            }
                        }
                    }
                },
                "additionalProperties": false
            },
            "Datapoint": {
                "title": "Datapoint",
                "required": ["_t", "asset"],
                "type": "object",
                "properties": {
                    "_t": {
                        "enum": ["Datapoint"],
                        "type": "string"
                    },
                    "asset": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/FileAsset"
                            },
                            {
                                "$ref": "#/components/schemas/MultiAsset"
                            }
                        ],
                        "discriminator": {
                            "propertyName": "_t"
                        }
                    }
                },
                "additionalProperties": false
            }
        }
    }
}

Expected behavior

const MultiAsset: z.ZodType<MultiAsset> = z.object({
    _t: z.literal("MultiAsset"),
    assets: z.array(z.lazy(() => z.discriminatedUnion("_t", [FileAsset, MultiAsset]))),
})

Edit: my suggested solution has a type issue that i couldn't solve (discriminatedUnion expects options to be z.ZodObject but instead we are passing z.ZodType), so i have fallen back to use union instead of discriminatedUnion