networknt / json-schema-validator

A fast Java JSON schema validator that supports draft V4, V6, V7, V2019-09 and V2020-12
Apache License 2.0
807 stars 320 forks source link

ValidatorTypeCode.TYPE not applicable for version V202012 anymore #994

Closed duke4 closed 4 months ago

duke4 commented 4 months ago

With version 1.4.0 the ValidatorTypeCode.TYPE is not applicable for JSON Schema version V202012 anymore.

This leads to an error when calling JsonSchemaFactory.getSchema(jsonNode) for the following example schema (jsonNode):

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
        "textValue": {
            "type": [
                "string",
                "null"
            ],
            "isMandatory": true
        }
    }
}

In JsonSchema.read(schemaNode) the following error occurs for the type field:

java.lang.ClassCastException: class com.networknt.schema.AnnotationKeyword$Validator cannot be cast to class com.networknt.schema.TypeValidator (com.networknt.schema.AnnotationKeyword$Validator and com.networknt.schema.TypeValidator are in unnamed module of loader 'app')

Can someone tell me why the TypeValidator is only applicable for version MaxV7 and is there some way to fix this?

justin-tay commented 4 months ago

To support custom vocabularies, for V201909 and V202012 the keywords are moved into their respective vocabularies.

https://github.com/networknt/json-schema-validator/blob/aeb48e25927c4f622f93f3accdc87de1f9c1daa1/src/main/java/com/networknt/schema/Vocabulary.java#L79

I cannot replicate your issue so you will likely need to supply more information about what you are doing

package com.networknt.schema;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.SpecVersion.VersionFlag;
import com.networknt.schema.serialization.JsonMapperFactory;

public class Issue994Test {
    @Test
    void test() throws JsonMappingException, JsonProcessingException {
        String schemaData = "{\r\n"
                + "    \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n"
                + "    \"type\": \"object\",\r\n"
                + "    \"properties\": {\r\n"
                + "        \"textValue\": {\r\n"
                + "            \"type\": [\r\n"
                + "                \"string\",\r\n"
                + "                \"null\"\r\n"
                + "            ],\r\n"
                + "            \"isMandatory\": true\r\n"
                + "        }\r\n"
                + "    }\r\n"
                + "}";
        JsonNode schemaNode = JsonMapperFactory.getInstance().readTree(schemaData);
        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaNode);
        String inputData = "{\r\n"
                + "  \"textValue\": \"hello\"\r\n"
                + "}";
        System.out.println(schema.validate(inputData, InputFormat.JSON, OutputFormat.HIERARCHICAL));
    }
}
duke4 commented 4 months ago

The hint with the vocabularies made my code working 👍

To give more context: I build a schema factory with a custom meta schema like this now

// base on JsonMetaSchema.V202012 copy code below
JsonMetaSchema customMetaSchema = new JsonMetaSchema.Builder("https://json-schema.org/draft/2020-12/schema")
    .idKeyword("$id")
    .formats(Formats.DEFAULT)
    .keywords(ValidatorTypeCode.getKeywords(SpecVersion.VersionFlag.V202012))
    .keywords(Vocabulary.V202012_VALIDATION.getKeywords())
    // keywords that may validly exist, but have no validation aspect to them
    .keywords(Arrays.asList(
        new NonValidationKeyword("$schema"),
        new NonValidationKeyword("$id"),
        new NonValidationKeyword("title"),
        new NonValidationKeyword("description"),
        new NonValidationKeyword("default"),
        new NonValidationKeyword("definitions"),
        new NonValidationKeyword("$comment"),
        new NonValidationKeyword("$defs"),
        new NonValidationKeyword("$anchor"),
        new NonValidationKeyword("deprecated"),
        new NonValidationKeyword("contentMediaType"),
        new NonValidationKeyword("contentEncoding"),
        new NonValidationKeyword("examples"),
        new NonValidationKeyword("then"),
        new NonValidationKeyword("else"),
        new NonValidationKeyword("additionalItems")
    ))
    // add your custom keyword
    .keyword(new IsMandatoryKeyword())
    .build();

JsonSchemaFactory jsonSchemaFactory = new JsonSchemaFactory.Builder().defaultMetaSchemaIri(customMetaSchema.getIri())
    .metaSchema(customMetaSchema)
    .build();

JsonSchema jsonSchema = jsonSchemaFactory.getSchema(jsonNode);

Maybe there is some more room for improvement?

justin-tay commented 4 months ago

The JsonMetaSchema builder accepts a JsonMetaSchema instance as a base if you want to modify an existing one.

JsonMetaSchema metaSchema = JsonMetaSchema.builder(JsonMetaSchema.getV202012()).keyword(new IsMandatoryKeyword())).build();
duke4 commented 4 months ago

Thank you. This works very well 🥳