3

I'm using a JSON Schemajsonschema to validate JSON records. Here's an example schema. It has only two cases here, but imagine a similar scenario where it instead has a hundred cases laid out like these.

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "oneOf": [
      {
        "type": "object",
        "required": ["a", "b", "c"],
        "properties": {
          "a": {"type": "integer", "enum": [0]},
          "b": {"type": "integer", "enum": [0, 2, 4, 6, 8]},
          "c": {"type": "string", "enum": ["always the same"]}
        }
      },
      {
        "type": "object",
        "required": ["a", "b", "c"],
        "properties": {
          "a": {"type": "integer", "enum": [1]},
          "b": {"type": "integer", "enum": [1, 3, 5, 7, 9]},
          "c": {"type": "string", "enum": ["always the same"]}
        }
      }
    ]
}

The key issue is the duplication of the "c" field. I'd like to be able to switch-case on "a", validating for a corresponding "b", but having "c" always remain the same. I don't want to have to spell out "c" a hundred different times. Is this possible to do?

Thanks!

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
liszt
  • 1,139
  • 1
  • 9
  • 17

2 Answers2

6

Yes, it can be done. In fact, it's good practice to only put in anyOf/oneOf the parts that change.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "properties": {
    "c": { "const": "always the same" }
  },
  "required": ["a", "b", "c"],
  "anyOf": [
    {
      "properties": {
        "a": { "const": 0 },
        "b": { "enum": [0, 2, 4, 6, 8] }
      }
    },
    {
      "properties": {
        "a": { "const": 1 },
        "b": { "enum": [1, 3, 5, 7, 9] }
      }
    }
  ]
}
Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
  • 1
    With draft-7, you can also use `const` rather than an enum with a single value. – Relequestual Oct 23 '18 at 08:34
  • 1
    Ah yes, I cleaned up some things, but I missed that one. `const` was added in draft-06 for anyone who needs to know. – Jason Desrosiers Oct 23 '18 at 18:13
  • Can I ask why you would choose to use `anyOf` rather than `oneOf` in this case? I can see that `anyOf` would work just fine since the defining property only has one value, but it seems like `oneOf` would be less error prone since it would require that the value of `a` is different in each subschema. – Vorticity Apr 19 '23 at 16:53
  • @Vorticity `anyOf` can be evaluated more efficiently by a validator. With `anyOf`, the validator can stop evaluating the `anyOf` schemas once it finds a match (in can short-circuit). With `oneOf`, the validator has to check every schema in `oneOf` to ensure that one and only one of the schemas match. In cases like this where the schemas are mutually exclusive, `oneOf` is doing unnecessary work. – Jason Desrosiers Apr 19 '23 at 19:59
  • In any case, I discourage this pattern these days in favor of `if`/`then`/`else` which is more efficient and will produce better error messages. https://stackoverflow.com/a/38781027/1320693 – Jason Desrosiers Apr 19 '23 at 20:01
0

A switch statement doesn't exist in JSON Schema. There was a long discussion about a proposal for it here. Without it, there are a couple of possible ways to provide this functionality:

anyOf / oneOf

The solution suggested by Jason Desrosiers will do the job but with a couple of caveats:

  1. You can only have a "default" case by manually constructing it with the other possible values. For the example in Jason's answer, this might look something like this:
      "properties": {
        "a": { "not": { "enum": [0, 1] } },
        "b": { "enum": [10, 11, 12] }
      }
  1. If using oneOf, care is needed to make sure all cases are mutually exclusive and cannot overlap.

if...then

Personally, I prefer a set of if...then fragments (surrounded by an allOf) instead. This has the same caveats as above. One advantage is it makes it clear which property is being "switched" on. Another is if using a validator such as ajv, the validation messages received when the JSON fails validation will pinpoint the issue better. If an anyOf or oneOf is used, you will simply get a validation message that the whole thing hasn't been met, which makes the cause less clear. But using if fragments, you are told which one hasn't been met and why. Here is Jason's example converted into this syntax:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "properties": {
    "c": { "const": "always the same" }
  },
  "required": ["a", "b", "c"],
  "allOf": [
    {
      "if": {
        "properties": {
          "a": { "const": 0 },
        }
      },
      "then": {
        "properties": {
          "b": { "enum": [0, 2, 4, 6, 8] }
        }
      }
    },
    {
      "if": {
        "properties": {
          "a": { "const": 1 },
        }
      },
      "then": {
        "properties": {
          "b": { "enum": [1, 3, 5, 7, 9] }
        }
      }
    }
  ]
}
Steve Chambers
  • 37,270
  • 24
  • 156
  • 208