-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
I have a model where text values can include optional localization via key suffixes:
{
// The default value for whatever the primary language is
label: "Purple",
// Localization via a ISO language code suffix on the key
"label:en": "Purple",
"label:ru": "Пурпурный",
"label:zh": "紫色"
}In terms of types, I end up with something like:
type WithLocalizedLabel = {
label: string;
[`label:${string}`]: string;
}In JSON Schema, I'd use:
{
patternProperties: {
"^label:[a-z]{2}$": { type: "string" },
},
properties: {
label: { type: "string" }
},
type: "object"
}For a zod validator, I use:
const zLabeled = z.object({ label: z.string() });
const zLocalizedLabeled = z.record(z.regex(/^label:[a-z]{2}$/), z.string());
const zWithLocalizedLabel = zLabeled.and(zLocalizedLabeled);That code generates the expected TypeScript types. The problem is with the generated JSON Schema. You get output like:
{
$defs: {
Labeled: {
properties: {
label: { type: "string" }
},
type: "object"
},
LocalizedLabeled: {
additionalProperties: {
type: "string"
},
propertyNames: {
pattern: "^label:[a-z]{2}$"
type: "string"
},
type: "object"
},
WithLocalizedLabel: {
allOf: [
{ $ref: "#/$defs/Labeled" },
{ $ref: "#/$defs/LocalizedLabeled" },
]
}
}
}The problem there is, of course, that allOf doesn't really mean quite the same as &. It just merges the two schemas, instead. So what you end up with is effectively:
{
properties: {
label: { type: "string" }
},
additionalProperties: {
type: "string"
},
propertyNames: {
pattern: "^label:[a-z]{2}$"
type: "string"
},
type: "object"
}This causes problems as label does not match the pattern.
Instead, what I'd actually like to end up with is for the record JSON Schema to look like:
{
properties: {
label: { type: "string" }
},
patternProperties: {
"^label:[a-z]{2}$": { type: "string" }
},
type: "object"
}I know one workaround would be to modify the pattern to make the suffix optional. But an option to trigger the use of patternProperties instead would also allow me to use strict i.e. additionalProperties: false, which would be nice.