recursivevirus
recursivevirus

Reputation: 27

How do I use an optional boolean property as a hint to require another property?

Imagine a JSON document with only two possible properties:

  1. persist: a boolean flag. defaults to true
  2. dbName: a string.

When persist is true, the dbName property must be present in the document. When persist is false, the dbName property must not be present in the document.

Seems simple enough--here's the schema I've come up with:

{
    "description": "Example",
    "$id": "https://example.com/example",
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "persist": {
            "type": "boolean"
        },
        "dbName": {
            "type": "string"
        }
    },
    "additionalProperties": false,
    "oneOf": [
        {
            "$comment": "persist attr is present and == false, dbName should not be present",
            "properties": {
                "persist": {
                    "enum": [
                        false
                    ]
                }
            },
            "not": {
                "required": [
                    "dbName"
                ]
            }
        },
        {
            "$comment": "persist attr is present and == true, require dbName",
            "properties": {
                "persist": {
                    "enum": [
                        true
                    ]
                }
            },
            "required": [
                "dbName"
            ]
        },
        {
            "$comment": "persist attr is missing, its default value is true, require dbName",
            "properties": {
                "persist": false
            },
            "required": [
                "dbName"
            ]
        }
    ]
}

It almost works. Three test documents to exercise the three possible cases:

{ "persist": true, "dbName": "dn" } --> Valid

{ "persist": false } --> Valid

{ "dbName": "dn" } --> Invalid with the errors below:

[{
        keyword: 'not',
        dataPath: '',
        schemaPath: '#/oneOf/0/not',
        params: {},
        message: 'should NOT be valid'
    },
    {
        keyword: 'oneOf',
        dataPath: '',
        schemaPath: '#/oneOf',
        params: {
            passingSchemas: [Array]
        },
        message: 'should match exactly one schema in oneOf'
    }
]

Through trial and error, it's clear that this last document is actually matching two of the conditions in the oneOf: oneOf/1 and oneOf/2.

I have no idea why it would match oneOf/1 since the persist property is absent from the json document. I'm sure I'm missing something obvious, any help is much appreciated.

Upvotes: 0

Views: 887

Answers (1)

Relequestual
Relequestual

Reputation: 12335

In your comments for your oneOf subschemas, you have said you expect the schema to validate that the persist attribute is present, but it does not.

Let's take a look at why this is the case...

Validation succeeds if, for each name that appears in both the
instance and as a name within this keyword's value, the child
instance for that name successfully validates against the
corresponding schema.

https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.5.4

This means that, the value subschemas are applied to the instance values in the object IF the corrosponding key exists. If it does not, then it's not applied.

As such, if you discount properties from oneOf/1 and oneOf/2, they are equivilent subschemas.

What you want for oneOf/2 is...

{
  "$comment": "persist attr is present and == true, require dbName",
  "properties": {
    "persist": {
      "const": true
    }
  },
  "required": [
    "persist",
    "dbName"
  ]
}

Here's a demo: https://jsonschema.dev/s/cqMDM

You'll want to make the same change to oneOf/0 too.

If you don't specify persist as required, the subschema will not be checking if the key exists or not.

Upvotes: 1

Related Questions