JSON Schema: Allow multiple types in array while specifying the order and amount of the types

I understand that this kind of JSON structure isn’t very good, but this is merely for myself for convenience while still verifying that what I create is valid.

The goal JSON files that SHOULD validate are the following;

[
  [ "string1" ]
]
[
  [ "string1", true ]
]
[
  [ "string1", true, "string2" ]
]

Each of these inner arrays MAY additionally contain another array as the final item. Said array will simply have the same validation as the root array ("$ref": "#"). There MUST be at least 1 item in the root array.

This means that these JSON files SHOULD NOT validate;

[

]
[
  [ "string1", [] ]
]

Declaring which types are allowed in the array items is fairly easy in a schema file, and I’ve done so right here;

{
  "title": "",
  "type": "array",
  "minItems": 1,
  "items": {
    "type": "array",
    "items": {
      "type": [ "string", "boolean", "array" ],
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "boolean"
        },
        {
          "type": "array",
          "$ref": "#"
        }
      ]
    }
  }
}

Unfortunately, this obviously does not validate the exact order and amount of the types. Is something like that even possible with something like the oneOf property? Maybe some combination of if and then?

It looks like you’re headed the right direction. You have the basic structure, but there are a few redundancies I’d like to point out.

{
  "title": "",
  "type": "array",
  "minItems": 1,
  "items": {
    "type": "array",
    "items": {
      "type": [ "string", "boolean", "array" ], // [1]
      "anyOf": [ // [2]
        {
          "type": "string"
        },
        {
          "type": "boolean"
        },
        {
          "type": "array", // [3]
          "$ref": "#"
        }
      ]
    }
  }
}
  1. This is just specifying the same as the oneOf. It’s not adding any extra validation and can be removed.
  2. anyOf isn’t going to help with specifying order. This will allow these elements in any order. More on how to ensure order below.
  3. Since the target of the $ref (the root) already declares type, this is unnecessary and can be removed.

Also, I’m not sure what version of JSON Schema you’re using. The keywords I tell you to use will depend on this. I’ll answer with draft 2020-12 keywords.

Item order

If you know the length of the inner array, you can use the prefixItems keyword to specify each item in an index-based fashion. Then items will address any other items.

{
  "type": "array",
  "prefixItems": [
    { "type": "boolean" },
    { "type": "string" },
    { "type": "boolean" }
  ],
  "items": { "type": "integer" }
}

This schema will require a boolean, then a string, then a boolean, and then any number of integers. These are all valid:

[ true, "string", false, 1, 3, 5 ]
[ true, "string" ]
[ true, "string", false ]

These are invalid:

[ true, "string", 1, 2, 3 ]

Note that prefixItems doesn’t require that there are three items, just that the items that are there match those subschemas.

Unfortunately, there’s not a way to specify a schema for the last item. So if the array can be any length, then you’re a bit out of luck.

A verbose option

If you don’t know how many items you’re going have, but you know there’s an upper limit, you can do something like this:

{
  "title": "",
  "type": "array",
  "minItems": 1,
  "items": {
    "type": "array",
    "anyOf": [
      {
        "prefixItems": [
          { "type": "string" }
        ]
      },
      {
        "prefixItems": [
          { "type": "string" },
          { "type": "boolean" }
        ]
      },
      {
        "prefixItems": [
          { "type": "string" },
          { "type": "boolean" },
          { "type": "string" }
        ]
      }
    ],
    "unevaluatedItems": { "$ref": "#" }
  }
}

The anyOf subschemas build up the options one item at a time. You only have to match against one of them, then unevaluatedItems operates like items from above (there’s a technical reason you need the different keyword, but that’s a blog post worth of explanation).

This will validate all of the ones you have in your question and these:

[ "string", [ ["string"] ] ]
[ "string", false, [ ["string"] ] ]
[ "string", false, "string", [ ["string"] ] ]

Leave a Comment