Skip to content

oneOf considers a document invalid unless it matches the first subschema #456

@myronmarston

Description

@myronmarston

The issue I'm seeing can be observed via this test class (written in kotlin):

package networknt.exampletest

import com.fasterxml.jackson.databind.ObjectMapper
import com.networknt.schema.JsonSchema
import com.networknt.schema.JsonSchemaFactory
import com.networknt.schema.SpecVersionDetector
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class JsonSchemaValidatorTest {
  val objectMapper = ObjectMapper()

  val validDocWithT2Type = objectMapper.readTree("""
      {
        "id": "abc",
        "details": {
          "__typename": "T2",
          "name": "Bob"
        }
      }
    """)

  val validDocWithT3Type = objectMapper.readTree("""
      {
        "id": "def",
        "details": {
          "__typename": "T3",
          "description": "Something"
        }
      }
    """)

  val t2TypeSchema = """
    {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "__typename": {"const": "T2"}
      },
      "required": ["name", "__typename"]
    }
  """

  val t3TypeSchema = """
    {
      "type": "object",
      "properties": {
        "description": {"type": "string"},
        "__typename": {"const": "T3"}
      },
      "required": ["description", "__typename"]
    }
  """

  @Test fun oneOfWithT2AndT3_T2DocIsValid() {
    val schema = buildSchema("oneOf", t2TypeSchema, t3TypeSchema)

    assertThat(schema.validate(validDocWithT2Type)).isEmpty()
  }

  @Test fun oneOfWithT2AndT3_T3DocIsValid() {
    val schema = buildSchema("oneOf", t2TypeSchema, t3TypeSchema)

    assertThat(schema.validate(validDocWithT3Type)).isEmpty()
  }

  @Test fun oneOfWithT3AndT2_T2DocIsValid() {
    val schema = buildSchema("oneOf", t3TypeSchema, t2TypeSchema)

    assertThat(schema.validate(validDocWithT2Type)).isEmpty()
  }

  @Test fun oneOfWithT3AndT3_T3DocIsValid() {
    val schema = buildSchema("oneOf", t3TypeSchema, t2TypeSchema)

    assertThat(schema.validate(validDocWithT3Type)).isEmpty()
  }

  @Test fun anyOfWithT2AndT3_T2DocIsValid() {
    val schema = buildSchema("anyOf", t2TypeSchema, t3TypeSchema)

    assertThat(schema.validate(validDocWithT2Type)).isEmpty()
  }

  @Test fun anyOfWithT2AndT3_T3DocIsValid() {
    val schema = buildSchema("anyOf", t2TypeSchema, t3TypeSchema)

    assertThat(schema.validate(validDocWithT3Type)).isEmpty()
  }

  @Test fun anyOfWithT3AndT2_T2DocIsValid() {
    val schema = buildSchema("anyOf", t3TypeSchema, t2TypeSchema)

    assertThat(schema.validate(validDocWithT2Type)).isEmpty()
  }

  @Test fun anyOfWithT3AndT3_T3DocIsValid() {
    val schema = buildSchema("anyOf", t3TypeSchema, t2TypeSchema)

    assertThat(schema.validate(validDocWithT3Type)).isEmpty()
  }

  private fun buildSchema(keyword: String, subschema1: String, subschema2: String): JsonSchema {
    val schemaString = """
      {
        "${'$'}schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "T1": {
            "type": "object",
            "properties": {
              "id": "string",
              "details": {
                "$keyword": [$subschema1, $subschema2]
              }
            }
          }
        }
      } 
    """

    val rootNode = objectMapper.readTree(schemaString)
    val definitionsNode = rootNode.get("definitions")!!
    val specVersion = SpecVersionDetector.detect(rootNode)
    val schemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(specVersion))
        .objectMapper(objectMapper)
        .build()

    return schemaFactory.getSchema(definitionsNode.get("T1"))
  }
}

When I run this, all but 2 tests pass. Here are the failures:

oneOfWithT2AndT3_T3DocIsValid:

Expecting empty but was: [$.details.name: is missing but it is required]
oneOfWithT3AndT2_T2DocIsValid:

Expecting empty but was: [$.details.description: is missing but it is required]

As you can see, when the schema uses anyOf, then documents matching either of the 2 subschemas are valid. But when the schema uses oneOf, the validator only considers a document that matches the first subschema to be valid. When we change the order of the subschemas, it changes which of the two example documents are considered valid.

My understanding of oneOf is that a document should be considered valid so long as it matches exactly one of the subschemas (and it doesn't matter which one).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions