zod-to-json-schema
zod-to-json-schema copied to clipboard
Certain schemas to define a record's keys are discarded
Back again!
JSON obviously doesn't support records, so they're represented as objects with restrictions (where possible) on the property names and values. The package's handling is pretty good, but it seems to give up in two cases I've found. In both cases, the package converts the schemas correctly outside a record key, so I'm unsure why that same schema can't be simply bundled into JSON Schema's propertyNames
property.
I unfortunately haven't managed to work out which part of the code actually causes this behaviour, though. I presume something about parsers/record.ts
, since the schemas are converted properly in other environments, but I didn't see anything pop out as odd!
Minimal reproducible examples
Examples below using CJS require()
so you can test in the REPL. I've used the same schema in both the record's keys and values to highlight that the issue only occurs with the former.
Composed schemas
The inclusion of a union or intersection causes the key schema to be discarded.
const { z } = require("zod");
const { zodToJsonSchema } = require("zod-to-json-schema");
const schema = z.string().url().or(z.string().email());
const zodSchema = z.record(schema, schema);
const jsonSchema = zodToJsonSchema(zodSchema);
console.log(JSON.stringify(jsonSchema, null, " "));
// Output
{
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string",
"format": "uri"
},
{
"type": "string",
"format": "email"
}
]
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
// Expected
{
"type": "object",
"additionalProperties": {
"anyOf": [
{
"type": "string",
"format": "uri"
},
{
"type": "string",
"format": "email"
}
]
},
"propertyNames": {
"anyOf": [
{
"type": "string",
"format": "email"
},
{
"type": "string",
"format": "uri"
}
]
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
z.literal()
z.literal()
causes that schema to be discarded, rather than being correctly converted to JSON Schema's const
.
The example below is fairly pathological, since the actual object being described here is just { foo?: "foo" }
. The case in which I actually encountered the bug, before reducing it down, was the slightly more reasonable
z.record(externalStringSchema.or(z.literal("specialCase")), valueSchema)
This is a composed schema as above, but I determined that any amount of z.literal()
causes a problem. So, although I do think the below case can fall under a wontfix/user error, I think it's probably still undesired due to the higher-order ramifications.
const { z } = require("zod");
const { zodToJsonSchema } = require("zod-to-json-schema");
const schema = z.literal("foo");
const zodSchema = z.record(schema, schema);
const jsonSchema = zodToJsonSchema(zodSchema);
console.log(JSON.stringify(jsonSchema, null, " "));
// Output
{
"type": "object",
"additionalProperties": {
"type": "string",
"const": "foo"
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
// Expected
{
"type": "object",
"additionalProperties": {
"type": "string",
"const": "foo"
},
"propertyNames": {
"type": "string",
"const": "foo"
},
"$schema": "http://json-schema.org/draft-07/schema#"
}