I've got a data structure I'm attempting to write a JSON Schema for, where many elements contain strings translated into one or more languages, here's a minimal example with English and French:
{
"supportedLanguages": ["en", "fr"],
"serviceName": {
"en": "My App",
"fr": "Mon Application"
},
"customerSupportEmail": {
"en": "help@myapp.com"
}
}
If I want to specify conditions, using JSON schema, so that "if English appears in supportedLanguages, every translated string (object with one key per language) must contain an English. I can do that per property, like this:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "example",
"type": "object",
"properties": {
"supportedLanguages": {
"type": "array",
"items": {
"type": "string",
"enum": ["en", "fr", "de"]
},
"minItems": 1,
"uniqueItems": true
},
"serviceName": {
"type": "object",
"properties": {
"en": {
"type": "string"
},
"fr": {
"type": "string"
},
"de": {
"type": "string"
}
}
},
"customerSupportEmail": {
"type": "object",
"properties": {
"en": {
"type": "string"
},
"fr": {
"type": "string"
},
"de": {
"type": "string"
}
}
}
},
"allOf": [
{
"if": {
"properties": {
"supportedLanguages": {
"type": "array",
"contains": {
"const": "en"
}
}
}
},
"then": {
"properties": {
"serviceName": {
"type": "object",
"required": ["en"]
},
"customerSupportEmail": {
"type": "object",
"required": ["en"]
}
}
}
}
(... others, one per supported language, ommitted to make this snippet shorter)
]
}
This example works correctly, failing validation when a supportedLanguage value is present which is not the key for one of the fields.
Ideally I'd like to split the logic into a definition ($def
), so it can be reused between many properties.
I tried doing it as follows:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "example",
"type": "object",
"properties": {
"supportedLanguages": {
"type": "array",
"items": {
"type": "string",
"enum": ["en", "fr", "de"]
},
"minItems": 1,
"uniqueItems": true
},
"serviceName": {
"$ref": "#/$defs/translatedString"
},
"customerSupportEmail": {
"$ref": "#/$defs/translatedString"
}
},
"$defs": {
"translatedString": {
"type": "object",
"properties": {
"en": {
"type": "string"
},
"fr": {
"type": "string"
},
"de": {
"type": "string"
}
}
}
},
"allOf": [
{
"if": {
"properties": {
"supportedLanguages": {
"type": "array",
"contains": {
"const": "en"
}
}
}
},
"then": {
"$defs": {
"translatedString": {
"type": "object",
"required": ["en"]
}
}
}
},
(... skipped the other languages again)
]
}
but, testing with ajv in Node, this does not work.
Is there a way to add conditions to the definition inside the if-then-else structure?
You can modularize everything into $defs
. here's an example of yours
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "example",
"type": "object",
"properties": {
"supportedLanguages": {
"type": "array",
"items": {
"type": "string",
"enum": ["en","fr","de"]
},
"minItems": 1,
"uniqueItems": true
},
"serviceName": {
"$ref": "#/$defs/lang_kvType"
},
"customerSupportEmail": {
"$ref": "#/$defs/lang_kvType"
}
},
"allOf": [
{
"$ref": "#/$defs/english_lang"
},
{
"$ref": "#/$defs/french_lang"
},
{
"$ref": "#/$defs/german_lang"
}
],
"$defs": {
"lang_kvType": {
"title": "Language Key Value Pair",
"propertyNames": {
"$ref": "#/$defs/CountryCodeType"
},
"additionalProperties": {
"type": "string"
}
},
"CountryCodeType": {
"title": "CountryCodeType",
"description": "The 2 character country code. As per ISO 3166-1 Alpha 2",
"type": "string",
"maxLength": 2,
"minLength": 2
},
"english_lang": {
"if": {
"required": [
"supportedLanguages"
],
"properties": {
"supportedLanguages": {
"type": "array",
"contains": {
"const": "en"
}
}
}
},
"then": {
"properties": {
"serviceName": {
"type": "object",
"required": [
"en"
]
},
"customerSupportEmail": {
"type": "object",
"required": [
"en"
]
}
}
}
},
"french_lang": {
"if": {
"required": [
"supportedLanguages"
],
"properties": {
"supportedLanguages": {
"type": "array",
"contains": {
"const": "fr"
}
}
}
},
"then": {
"properties": {
"serviceName": {
"type": "object",
"required": [
"fr"
]
},
"customerSupportEmail": {
"type": "object",
"required": [
"fr"
]
}
}
}
},
"german_lang": {
"if": {
"required": [
"supportedLanguages"
],
"properties": {
"supportedLanguages": {
"type": "array",
"contains": {
"const": "de"
}
}
}
},
"then": {
"properties": {
"serviceName": {
"type": "object",
"required": [
"de"
]
},
"customerSupportEmail": {
"type": "object",
"required": [
"de"
]
}
}
}
}
}
}
EDIT, if you want to use enumerations for the country attribute names you can do this
This will constrain the naming to only the enumerations provided, rather than a 2 character string value which I previously defined as CountryCodeType
.
"$defs": {
"lang_kvType": {
"title": "Language Key Value Pair",
"propertyNames": {
"enum": ["ar", "ab", "ac", "en", "fr", "de"....]
},
"additionalProperties": {
"type": "string"
}
}