2

What I'm trying to accomplish in json-schema: when the property enabled is true, certain other properties should be required. When false, those properties should be disallowed.

Here's my json-schema:

{
  "type": "object",
  "properties": {
    "enabled": { "type": "boolean" }
  },
  "required" : ["enabled"],
  "additionalProperties" : false,
  "if": {
    "properties": {
      "enabled": true
    }
  },
  "then": { 
    "properties": {
      "description" : { "type" : "string" },
      "count": { "type": "number" }
    },
    "required" : ["description", "count"]
  }
}

Validating using ajv version 6.5, this had the result of requiring count, etc. regardless of the value of enabled. For instance, for data:

{ "enabled": false }

My validation errors are:

[ { keyword: 'required',
    dataPath: '',
    schemaPath: '#/then/required',
    params: { missingProperty: 'description' },
    message: 'should have required property \'description\'' },
  { keyword: 'required',
    dataPath: '',
    schemaPath: '#/then/required',
    params: { missingProperty: 'count' },
    message: 'should have required property \'count\'' },
  { keyword: 'if',
    dataPath: '',
    schemaPath: '#/if',
    params: { failingKeyword: 'then' },
    message: 'should match "then" schema' } ]

How can I accomplish this using json-schema draft-7?

Note that this question is similar to, but has more stringent requirements than:
jsonSchema attribute conditionally required.

Clay Bridges
  • 11,602
  • 10
  • 68
  • 118
  • Possible duplicate of [jsonSchema attribute conditionally required](https://stackoverflow.com/questions/38717933/jsonschema-attribute-conditionally-required) – Jason Desrosiers May 15 '18 at 03:08
  • @Jason Not a duplicate. It's similar, but this question has more stringent requirements. – Clay Bridges May 15 '18 at 16:53
  • I disagree. This is a pretty straightforward use-case for the strategy labeled "Enum". In fact, your answer below uses that strategy. Your answer should be marked as the accepted answer. It's much better than the `if`-`then`-`else` answer. – Jason Desrosiers May 16 '18 at 03:59
  • I agree to accept my own answer (in 5 hours), because it's probably a more elegant solution to my problem. I maintain this question is not a duplicate, because it is asking a different, if related, question. I read your (excellent) answer to the other question more than once, and still didn't know exactly how to solve my/this particular problem. While the applicability of a variant of the "Enum" strategy here may be obvious to a _skilled practitioner_, as a novice I can say it was not obvious to me. Better to have it spelled out for this case, to possibly save others that struggle. – Clay Bridges May 16 '18 at 19:57

3 Answers3

6

Try this schema:

{
  "type": "object",
  "properties": {
    "enabled": {
      "type": "boolean"
    }
  },
  "required": [
    "enabled"
  ],
  "if": {
    "properties": {
      "enabled": {
        "const": true
      }
    }
  },
  "then": {
    "properties": {
      "enabled": {
        "type": "boolean"
      },
      "description": {
        "type": "string"
      },
      "count": {
        "type": "number"
      },
      "additionalProperties": false
    },
    "required": [
      "description",
      "count"
    ]
  },
  "else": {
    "properties": {
      "enabled": {
        "type": "boolean"
      }
    },
    "additionalProperties": false
  }
}

If you need "additionalProperties": false you have to enumerate all properties in both then and else. If you can accept additional properties the schema could be simplier:

{
  "type": "object",
  "properties": {
    "enabled": {
      "type": "boolean"
    }
  },
  "required": [
    "enabled"
  ],
  "if": {
    "properties": {
      "enabled": {
        "const": true
      }
    }
  },
  "then": {
    "properties": {
      "description": {
        "type": "string"
      },
      "count": {
        "type": "number"
      }
    },
    "required": [
      "description",
      "count"
    ]
  }
}

I checked with ajv cli.

Valid: {"enabled": false}

Invalid: {"enabled": true}

Valid: {"enabled": true, "description":"hi", "count":1}

vearutop
  • 3,924
  • 24
  • 41
1

This was inspired by vearutop's excellent answer. I think it might be a little shorter, and accomplishes my stated purpose.

{
  "type": "object",
  "oneOf" : [
    {
      "properties": {
        "enabled": { "const": false }
      },
      "required": ["enabled"],
      "additionalProperties": false
    },
    {
      "properties": {
        "enabled": { "const": true },
        "description": { "type": "string" },
        "count": { "type": "number" }
      },
      "required": [ "enabled", "description", "count"],
      "additionalProperties": false
    }
  ]
}

As pointed out in the comments, this is a specific variant of the Enum strategy spelled out in this answer.

Clay Bridges
  • 11,602
  • 10
  • 68
  • 118
1

To achieve that, in the if statement you need to use the const keyword, so the schema will look like:

{
  "type": "object",
  "properties": {
    "enabled": { "type": "boolean" }
  },
  "required" : ["enabled"],
  "additionalProperties" : false,
  "if": {
    "properties": {
      "enabled": {"const": true}
    }
  },
  "then": { 
    "properties": {
      "description" : { "type" : "string" },
      "count": { "type": "number" }
    },
    "required" : ["description", "count"]
  }
}
fpenim
  • 491
  • 1
  • 4
  • 14