json-schema-validator icon indicating copy to clipboard operation
json-schema-validator copied to clipboard

Applying Subschemas Conditionally not working as expected using http://json-schema.org/ example

Open suren39 opened this issue 3 years ago • 3 comments

Hi, following example is not working as expected: (very bottom of the page) http://json-schema.org/understanding-json-schema/reference/conditionals.html#id7

{ "type": "object", "properties": { "restaurantType": { "enum": ["fast-food", "sit-down"] }, "total": { "type": "number" }, "tip": { "type": "number" } }, "anyOf": [ { "not": { "properties": { "restaurantType": { "const": "sit-down" } }, "required": ["restaurantType"] } }, { "required": ["tip"] } ] }

Following input should pass, but it is not...

{ "restaurantType": "fast-food", "total": 6.99 }

Seeing following error: <"$: should not be valid to the schema "not" : {"properties":{"restaurantType":{"const":"sit-down"}},"required":["restaurantType"]}">

Tried an online validator and it is working there: https://www.jsonschemavalidator.net/

Thank you!

suren39 avatar Mar 29 '22 23:03 suren39

Oh the issue seems to be isolated to Draft 4..

Failed here JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);

Passed here JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);

suren39 avatar Mar 29 '22 23:03 suren39

I think this is one of the issues that we are using some modern keywords that v4 is not supported. I am wondering if you could submit a PR to reproduce the issue so that other developers can debug it and see what is the difference between v4 and v7. Thanks.

stevehu avatar Mar 30 '22 04:03 stevehu

I can provide my unit test..

Service class

`import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service;

import java.io.IOException; import java.io.InputStream; import java.util.Objects; import java.util.Set;

@Service public class JsonSchemaValidator {

private ObjectMapper objectMapper = new ObjectMapper();

public String validate(String json, String schemaName) {
    final JsonSchema schema =
            getJsonSchemaFromClasspath(schemaName);
    if (Objects.isNull(schema)) {
        throw new IllegalStateException("Schema not found: " + schemaName);
    }
    final JsonNode jsonNode =
            getJsonNodeFromStringContent(json);
    final Set<ValidationMessage> errors = schema.validate(jsonNode);
    if (CollectionUtils.isEmpty(errors)) {
        return "No Errors";
    }
    return errors.iterator().next().getMessage();
}

public String validateWithStringSchema(String json, String schemaContent) {
    final JsonSchema schema =
            getJsonSchemaFromStringContent(schemaContent);
    if (Objects.isNull(schema)) {
        throw new IllegalStateException("Schema not found");
    }
    final JsonNode jsonNode =
            getJsonNodeFromStringContent(json);
    final Set<ValidationMessage> errors = schema.validate(jsonNode);
    if (CollectionUtils.isEmpty(errors)) {
        return "No Errors";
    }
    return errors.iterator().next().getMessage();
}

protected JsonSchema getJsonSchemaFromStringContent(String schemaContent) {
    JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
    return factory.getSchema(schemaContent);
}

protected JsonSchema getJsonSchemaFromClasspath(String name) {
    JsonSchemaFactory factory =
            JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4);
    InputStream is = Thread.currentThread().getContextClassLoader()
            .getResourceAsStream(name);
    return factory.getSchema(is);
}

protected JsonNode getJsonNodeFromStringContent(String content) {
    try {
        return objectMapper.readTree(content);
    } catch (JsonProcessingException e) {
        throw new IllegalStateException(e.getMessage());
    }
}

}`

Test

`import com.cdm.lend.leadgen.domain.demo.JsonSchemaValidator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.StreamUtils;

import java.nio.charset.Charset;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(MockitoExtension.class) class JsonSchemaValidatorV2Test {

private JsonSchemaValidator schemaValidator;
private ResourceLoader resourceLoader;

@BeforeEach
void setUp() {
    schemaValidator = new JsonSchemaValidator();
    resourceLoader = new DefaultResourceLoader();
}

@Test
void givenInquirySubmit_whenValidating_thenNoErrorsFound_V3() throws Exception {
    final String pageSubmit =
            getResourceString("models/submit_v2.json");
    System.out.println(pageSubmit);
    final String validateResult =
            schemaValidator.validate(pageSubmit,
                    "models/submit_schema_v3.json");
    assertThat(validateResult)
            .isEqualTo("No Errors");
}

private String getResourceString(String file) throws Exception {
    final Resource resource = resourceLoader.getResource(file);
    return StreamUtils.copyToString(resource.getInputStream(),
            Charset.defaultCharset());
}

}`

Schema { "type": "object", "properties": { "restaurantType": { "enum": ["fast-food", "sit-down"] }, "total": { "type": "number" }, "tip": { "type": "number" } }, "anyOf": [ { "not": { "properties": { "restaurantType": { "const": "sit-down" } }, "required": ["restaurantType"] } }, { "required": ["tip"] } ] }

Json { "restaurantType": "fast-food", "total": 6.99 }

suren39 avatar Mar 30 '22 20:03 suren39

@suren39 @stevehu The reason this test-case fails in Draft 4 is because the const keyword did not exist prior to Draft 6. I have verified that this test-case passes in all dialects after Draft 4.

fdutton avatar Jun 09 '23 13:06 fdutton