1*78c4dd6aSAndroid Build Coastguard Worker# Customizing Meta-Schemas, Vocabularies, Keywords and Formats 2*78c4dd6aSAndroid Build Coastguard Worker 3*78c4dd6aSAndroid Build Coastguard WorkerThe meta-schemas, vocabularies, keywords and formats can be customized with appropriate configuration of the `JsonSchemaFactory` that is used to create instances of `JsonSchema`. 4*78c4dd6aSAndroid Build Coastguard Worker 5*78c4dd6aSAndroid Build Coastguard Worker## Creating a custom keyword 6*78c4dd6aSAndroid Build Coastguard Worker 7*78c4dd6aSAndroid Build Coastguard WorkerA custom keyword can be implemented by implementing the `com.networknt.schema.Keyword` interface. 8*78c4dd6aSAndroid Build Coastguard Worker 9*78c4dd6aSAndroid Build Coastguard Worker```java 10*78c4dd6aSAndroid Build Coastguard Workerpublic class EqualsKeyword implements Keyword { 11*78c4dd6aSAndroid Build Coastguard Worker @Override 12*78c4dd6aSAndroid Build Coastguard Worker public String getValue() { 13*78c4dd6aSAndroid Build Coastguard Worker return "equals"; 14*78c4dd6aSAndroid Build Coastguard Worker } 15*78c4dd6aSAndroid Build Coastguard Worker @Override 16*78c4dd6aSAndroid Build Coastguard Worker public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, 17*78c4dd6aSAndroid Build Coastguard Worker JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) 18*78c4dd6aSAndroid Build Coastguard Worker throws JsonSchemaException, Exception { 19*78c4dd6aSAndroid Build Coastguard Worker return new EqualsValidator(schemaLocation, evaluationPath, schemaNode, parentSchema, this, validationContext, false); 20*78c4dd6aSAndroid Build Coastguard Worker } 21*78c4dd6aSAndroid Build Coastguard Worker} 22*78c4dd6aSAndroid Build Coastguard Worker``` 23*78c4dd6aSAndroid Build Coastguard Worker 24*78c4dd6aSAndroid Build Coastguard Worker```java 25*78c4dd6aSAndroid Build Coastguard Workerpublic class EqualsValidator extends BaseJsonValidator { 26*78c4dd6aSAndroid Build Coastguard Worker private static ErrorMessageType ERROR_MESSAGE_TYPE = new ErrorMessageType() { 27*78c4dd6aSAndroid Build Coastguard Worker @Override 28*78c4dd6aSAndroid Build Coastguard Worker public String getErrorCode() { 29*78c4dd6aSAndroid Build Coastguard Worker return "equals"; 30*78c4dd6aSAndroid Build Coastguard Worker } 31*78c4dd6aSAndroid Build Coastguard Worker }; 32*78c4dd6aSAndroid Build Coastguard Worker 33*78c4dd6aSAndroid Build Coastguard Worker private final String value; 34*78c4dd6aSAndroid Build Coastguard Worker public EqualsValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode, 35*78c4dd6aSAndroid Build Coastguard Worker JsonSchema parentSchema, Keyword keyword, 36*78c4dd6aSAndroid Build Coastguard Worker ValidationContext validationContext, boolean suppressSubSchemaRetrieval) { 37*78c4dd6aSAndroid Build Coastguard Worker super(schemaLocation, evaluationPath, schemaNode, parentSchema, ERROR_MESSAGE_TYPE, keyword, validationContext, 38*78c4dd6aSAndroid Build Coastguard Worker suppressSubSchemaRetrieval); 39*78c4dd6aSAndroid Build Coastguard Worker this.value = schemaNode.textValue(); 40*78c4dd6aSAndroid Build Coastguard Worker } 41*78c4dd6aSAndroid Build Coastguard Worker @Override 42*78c4dd6aSAndroid Build Coastguard Worker public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, 43*78c4dd6aSAndroid Build Coastguard Worker JsonNodePath instanceLocation) { 44*78c4dd6aSAndroid Build Coastguard Worker if (!node.asText().equals(value)) { 45*78c4dd6aSAndroid Build Coastguard Worker return Collections 46*78c4dd6aSAndroid Build Coastguard Worker .singleton(message().message("{0}: must be equal to ''{1}''") 47*78c4dd6aSAndroid Build Coastguard Worker .arguments(value) 48*78c4dd6aSAndroid Build Coastguard Worker .instanceLocation(instanceLocation).instanceNode(node).build()); 49*78c4dd6aSAndroid Build Coastguard Worker }; 50*78c4dd6aSAndroid Build Coastguard Worker return Collections.emptySet(); 51*78c4dd6aSAndroid Build Coastguard Worker } 52*78c4dd6aSAndroid Build Coastguard Worker} 53*78c4dd6aSAndroid Build Coastguard Worker``` 54*78c4dd6aSAndroid Build Coastguard Worker 55*78c4dd6aSAndroid Build Coastguard Worker## Adding a keyword to a standard dialect 56*78c4dd6aSAndroid Build Coastguard Worker 57*78c4dd6aSAndroid Build Coastguard WorkerA custom keyword can be added to a standard dialect by customizing its meta-schema which is identified by its IRI. 58*78c4dd6aSAndroid Build Coastguard Worker 59*78c4dd6aSAndroid Build Coastguard WorkerThe following adds a custom keyword to the Draft 2020-12 dialect. 60*78c4dd6aSAndroid Build Coastguard Worker 61*78c4dd6aSAndroid Build Coastguard Worker```java 62*78c4dd6aSAndroid Build Coastguard WorkerJsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) 63*78c4dd6aSAndroid Build Coastguard Worker .keyword(new EqualsKeyword()) 64*78c4dd6aSAndroid Build Coastguard Worker .build(); 65*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); 66*78c4dd6aSAndroid Build Coastguard Worker``` 67*78c4dd6aSAndroid Build Coastguard Worker 68*78c4dd6aSAndroid Build Coastguard Worker## Creating a custom meta-schema 69*78c4dd6aSAndroid Build Coastguard Worker 70*78c4dd6aSAndroid Build Coastguard WorkerA custom meta-schema can be created by using a standard dialect as a base. 71*78c4dd6aSAndroid Build Coastguard Worker 72*78c4dd6aSAndroid Build Coastguard WorkerThe following creates a custom meta-schema `https://www.example.com/schema` with a custom keyword using the Draft 2020-12 dialect as a base. 73*78c4dd6aSAndroid Build Coastguard Worker 74*78c4dd6aSAndroid Build Coastguard Worker```java 75*78c4dd6aSAndroid Build Coastguard WorkerJsonMetaSchema dialect = JsonMetaSchema.getV202012(); 76*78c4dd6aSAndroid Build Coastguard WorkerJsonMetaSchema metaSchema = JsonMetaSchema.builder("https://www.example.com/schema", dialect) 77*78c4dd6aSAndroid Build Coastguard Worker .keyword(new EqualsKeyword()) 78*78c4dd6aSAndroid Build Coastguard Worker .build(); 79*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); 80*78c4dd6aSAndroid Build Coastguard Worker``` 81*78c4dd6aSAndroid Build Coastguard Worker 82*78c4dd6aSAndroid Build Coastguard Worker## Associating vocabularies to a dialect 83*78c4dd6aSAndroid Build Coastguard Worker 84*78c4dd6aSAndroid Build Coastguard WorkerCustom vocabularies can be associated with a particular dialect by configuring a `com.networknt.schema.VocabularyFactory` on its meta-schema. 85*78c4dd6aSAndroid Build Coastguard Worker 86*78c4dd6aSAndroid Build Coastguard Worker```java 87*78c4dd6aSAndroid Build Coastguard WorkerVocabularyFactory vocabularyFactory = iri -> { 88*78c4dd6aSAndroid Build Coastguard Worker if ("https://www.example.com/vocab/equals".equals(iri)) { 89*78c4dd6aSAndroid Build Coastguard Worker return new Vocabulary("https://www.example.com/vocab/equals", new EqualsKeyword()); 90*78c4dd6aSAndroid Build Coastguard Worker } 91*78c4dd6aSAndroid Build Coastguard Worker return null; 92*78c4dd6aSAndroid Build Coastguard Worker}; 93*78c4dd6aSAndroid Build Coastguard WorkerJsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) 94*78c4dd6aSAndroid Build Coastguard Worker .vocabularyFactory(vocabularyFactory) 95*78c4dd6aSAndroid Build Coastguard Worker .build(); 96*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); 97*78c4dd6aSAndroid Build Coastguard Worker``` 98*78c4dd6aSAndroid Build Coastguard Worker 99*78c4dd6aSAndroid Build Coastguard WorkerThe following custom meta-schema `https://www.example.com/schema` will use the custom vocabulary `https://www.example.com/vocab/equals`. 100*78c4dd6aSAndroid Build Coastguard Worker 101*78c4dd6aSAndroid Build Coastguard Worker```json 102*78c4dd6aSAndroid Build Coastguard Worker{ 103*78c4dd6aSAndroid Build Coastguard Worker "$schema": "https://json-schema.org/draft/2020-12/schema", 104*78c4dd6aSAndroid Build Coastguard Worker "$id": "https://www.example.com/schema", 105*78c4dd6aSAndroid Build Coastguard Worker "$vocabulary": { 106*78c4dd6aSAndroid Build Coastguard Worker "https://www.example.com/vocab/equals": true, 107*78c4dd6aSAndroid Build Coastguard Worker "https://json-schema.org/draft/2020-12/vocab/applicator": true, 108*78c4dd6aSAndroid Build Coastguard Worker "https://json-schema.org/draft/2020-12/vocab/core": true 109*78c4dd6aSAndroid Build Coastguard Worker }, 110*78c4dd6aSAndroid Build Coastguard Worker "allOf": [ 111*78c4dd6aSAndroid Build Coastguard Worker { "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" }, 112*78c4dd6aSAndroid Build Coastguard Worker { "$ref": "https://json-schema.org/draft/2020-12/meta/core" } 113*78c4dd6aSAndroid Build Coastguard Worker ] 114*78c4dd6aSAndroid Build Coastguard Worker} 115*78c4dd6aSAndroid Build Coastguard Worker``` 116*78c4dd6aSAndroid Build Coastguard Worker 117*78c4dd6aSAndroid Build Coastguard WorkerNote that `"https://www.example.com/vocab/equals": true` means that if the vocabulary is unknown the meta-schema will fail to successfully load while `"https://www.example.com/vocab/equals": false` means that an unknown vocabulary will still successfully load. 118*78c4dd6aSAndroid Build Coastguard Worker 119*78c4dd6aSAndroid Build Coastguard Worker## Unknown keywords 120*78c4dd6aSAndroid Build Coastguard Worker 121*78c4dd6aSAndroid Build Coastguard WorkerBy default unknown keywords are treated as annotations. This can be customized by configuring a `com.networknt.schema.KeywordFactory` on its meta-schema. 122*78c4dd6aSAndroid Build Coastguard Worker 123*78c4dd6aSAndroid Build Coastguard WorkerThe following configuration will cause a `InvalidSchemaException` to be thrown if an unknown keyword is used. 124*78c4dd6aSAndroid Build Coastguard Worker 125*78c4dd6aSAndroid Build Coastguard Worker```java 126*78c4dd6aSAndroid Build Coastguard WorkerJsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) 127*78c4dd6aSAndroid Build Coastguard Worker .unknownKeywordFactory(DisallowUnknownKeywordFactory.getInstance()) 128*78c4dd6aSAndroid Build Coastguard Worker .build(); 129*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); 130*78c4dd6aSAndroid Build Coastguard Worker``` 131*78c4dd6aSAndroid Build Coastguard Worker 132*78c4dd6aSAndroid Build Coastguard Worker## Creating a custom format 133*78c4dd6aSAndroid Build Coastguard Worker 134*78c4dd6aSAndroid Build Coastguard WorkerA custom format can be implemented by implementing the `com.networknt.schema.Format` interface. 135*78c4dd6aSAndroid Build Coastguard Worker 136*78c4dd6aSAndroid Build Coastguard Worker```java 137*78c4dd6aSAndroid Build Coastguard Workerpublic class MatchNumberFormat implements Format { 138*78c4dd6aSAndroid Build Coastguard Worker private final BigDecimal compare; 139*78c4dd6aSAndroid Build Coastguard Worker 140*78c4dd6aSAndroid Build Coastguard Worker public MatchNumberFormat(BigDecimal compare) { 141*78c4dd6aSAndroid Build Coastguard Worker this.compare = compare; 142*78c4dd6aSAndroid Build Coastguard Worker } 143*78c4dd6aSAndroid Build Coastguard Worker @Override 144*78c4dd6aSAndroid Build Coastguard Worker public boolean matches(ExecutionContext executionContext, ValidationContext validationContext, JsonNode value) { 145*78c4dd6aSAndroid Build Coastguard Worker JsonType nodeType = TypeFactory.getValueNodeType(value, validationContext.getConfig()); 146*78c4dd6aSAndroid Build Coastguard Worker if (nodeType != JsonType.NUMBER && nodeType != JsonType.INTEGER) { 147*78c4dd6aSAndroid Build Coastguard Worker return true; 148*78c4dd6aSAndroid Build Coastguard Worker } 149*78c4dd6aSAndroid Build Coastguard Worker BigDecimal number = value.isBigDecimal() ? value.decimalValue() : BigDecimal.valueOf(value.doubleValue()); 150*78c4dd6aSAndroid Build Coastguard Worker number = new BigDecimal(number.toPlainString()); 151*78c4dd6aSAndroid Build Coastguard Worker return number.compareTo(compare) == 0; 152*78c4dd6aSAndroid Build Coastguard Worker } 153*78c4dd6aSAndroid Build Coastguard Worker @Override 154*78c4dd6aSAndroid Build Coastguard Worker public String getName() { 155*78c4dd6aSAndroid Build Coastguard Worker return "matchnumber"; 156*78c4dd6aSAndroid Build Coastguard Worker } 157*78c4dd6aSAndroid Build Coastguard Worker} 158*78c4dd6aSAndroid Build Coastguard Worker``` 159*78c4dd6aSAndroid Build Coastguard Worker 160*78c4dd6aSAndroid Build Coastguard Worker## Adding a format to a standard dialect 161*78c4dd6aSAndroid Build Coastguard Worker 162*78c4dd6aSAndroid Build Coastguard WorkerA custom format can be added to a standard dialect by customizing its meta-schema which is identified by its IRI. 163*78c4dd6aSAndroid Build Coastguard Worker 164*78c4dd6aSAndroid Build Coastguard WorkerThe following adds a custom format to the Draft 2020-12 dialect. 165*78c4dd6aSAndroid Build Coastguard Worker 166*78c4dd6aSAndroid Build Coastguard Worker```java 167*78c4dd6aSAndroid Build Coastguard WorkerJsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) 168*78c4dd6aSAndroid Build Coastguard Worker .format(new MatchNumberFormat(new BigDecimal("12345"))) 169*78c4dd6aSAndroid Build Coastguard Worker .build(); 170*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); 171*78c4dd6aSAndroid Build Coastguard Worker``` 172*78c4dd6aSAndroid Build Coastguard Worker 173*78c4dd6aSAndroid Build Coastguard Worker## Customizing the format keyword 174*78c4dd6aSAndroid Build Coastguard Worker 175*78c4dd6aSAndroid Build Coastguard WorkerThe format keyword implementation to use can be customized by supplying a `FormatKeywordFactory` to the meta-schema that creates an instance of the subclass of `FormatKeyword`. 176*78c4dd6aSAndroid Build Coastguard Worker 177*78c4dd6aSAndroid Build Coastguard Worker```java 178*78c4dd6aSAndroid Build Coastguard WorkerJsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()) 179*78c4dd6aSAndroid Build Coastguard Worker .formatKeywordFactory(CustomFormatKeyword::new) 180*78c4dd6aSAndroid Build Coastguard Worker .build(); 181*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder.metaSchema(metaSchema)); 182*78c4dd6aSAndroid Build Coastguard Worker``` 183*78c4dd6aSAndroid Build Coastguard Worker 184*78c4dd6aSAndroid Build Coastguard Worker## Unknown formats 185*78c4dd6aSAndroid Build Coastguard Worker 186*78c4dd6aSAndroid Build Coastguard WorkerBy default unknown formats are ignored unless the format assertion vocabulary is used for that meta-schema. Note that the format annotation vocabulary with the configuration to enable format assertions is not equivalent to the format assertion vocabulary. 187*78c4dd6aSAndroid Build Coastguard Worker 188*78c4dd6aSAndroid Build Coastguard WorkerTo ensure that errors are raised when unknown formats are used, the `SchemaValidatorsConfig` can be configured to set `format` as strict. 189*78c4dd6aSAndroid Build Coastguard Worker 190*78c4dd6aSAndroid Build Coastguard Worker 191*78c4dd6aSAndroid Build Coastguard Worker## Loading meta-schemas 192*78c4dd6aSAndroid Build Coastguard Worker 193*78c4dd6aSAndroid Build Coastguard WorkerBy default meta-schemas that aren't explicitly configured in the `JsonSchemaFactory` will be automatically loaded. 194*78c4dd6aSAndroid Build Coastguard Worker 195*78c4dd6aSAndroid Build Coastguard WorkerThis means that the following `JsonSchemaFactory` will still be able to process `$schema` with other dialects such as Draft 7 or Draft 2019-09 as the meta-schemas for those dialects will be automatically loaded. This will also attempt to load custom meta-schemas with custom vocabularies. Draft 2020-12 will be used by default if `$schema` is not defined. 196*78c4dd6aSAndroid Build Coastguard Worker 197*78c4dd6aSAndroid Build Coastguard Worker```java 198*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012); 199*78c4dd6aSAndroid Build Coastguard Worker``` 200*78c4dd6aSAndroid Build Coastguard Worker 201*78c4dd6aSAndroid Build Coastguard WorkerIf this is undesirable, for instance to restrict the meta-schemas used only to those explicitly configured in the `JsonSchemaFactory` a `com.networknt.schema.JsonMetaSchemaFactory` can be configured. 202*78c4dd6aSAndroid Build Coastguard Worker 203*78c4dd6aSAndroid Build Coastguard Worker```java 204*78c4dd6aSAndroid Build Coastguard WorkerJsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, 205*78c4dd6aSAndroid Build Coastguard Worker builder -> builder.metaSchemaFactory(DisallowUnknownJsonMetaSchemaFactory.getInstance())); 206*78c4dd6aSAndroid Build Coastguard Worker``` 207