Feature toggle configurations with JSONSchema

Note: I use the package validate-value for everything related to JSONSchema validation, which uses ajv under the hood. Thus the error messages and caveat mentioned here are based on my experience these packages. They do not necessarily reflect the JSONSchema spec and might not occur with other implementations.

JSONSchema has the feature one-of, which takes multiple schemas and validates that exactly one of these schemas matches.

JSONSchema also has the feature const, which takes a single value and validates that the property for which it is set has exactly this value.

These features can be combined for mutually exclusive configurations. For example, if you want a feature toggle, you’ll probably want your configuration to either be { "isEnabled": false } or { "isEnabled": true, "someOtherConfig": "foo" }. The key point being, that if isEnabled is false, no other properties next to it are allowed.

{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "isEnabled": {
          "type": "boolean",
          "const": false
        }
      },
      "required": [ "isEnabled" ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "isEnabled": {
          "type": "boolean",
          "const": true
        },
        "someOtherConfig": {
          "type": "string"
        }
      },
      "required": [ "isEnabled", "someOtherConfig" ],
      "additionalProperties": false
    }
  ]
}

Caveat

If the second part of the schema, the one where isEnabled is true and which contains additional properties, does not match for whatever reason, the error message will be confusing. Say you use the above schema to validate this data:

{
  "isEnabled": true,
  "someOtherConfig": 1337
}

Here someOtherConfig does not match the specified type string. So the second schema in oneOf does not match. So JSONSchema tries to match it against the first schema in oneOf and the resulting error message is unexpected additional property "someOtherConfig".

Workaround

This confusing error message is due to the fact that ajv bases its error message on the first schema given in oneOf. So put the schema that you want to be validated in detail first!

{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "isEnabled": {
          "type": "boolean",
          "const": true
        },
        "someOtherConfig": {
          "type": "string"
        }
      },
      "required": [ "isEnabled", "someOtherConfig" ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "isEnabled": {
          "type": "boolean",
          "const": false
        }
      },
      "required": [ "isEnabled" ],
      "additionalProperties": false
    }
  ]
}

Now if isEnabled is set to true, it will go into the first schema in anyOf and continue validation from there and thus give detailled error messages. Using this schema to validate the broken data above results in something along the lines of validation failed; "someOtherConfig" should be string.