17

Hi all and thanks in advance.

I am attempting to create a JSON schema to enforce an array to contain one A and B object and N C objects, where A and B are C objects and N is an integer inclusively between 0 and infinity.

Therefor :

[A, B] [A, B, C1] [A, B, C1, .., CN]

Are all valid, though :

[A] [A, C1] [A, C1, .., CN]

Are not valid.

To make clear, A and B must be present. C objects are optional, though you may have as many as you would like.

C object schemas :


{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "C Object",

  "type": "object",
  "required": ["id", "name"],

  "properties": {
    "id": {
      "type": "integer"
    },
    "name": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

So a C object is any valid JSON object containing only the properties "id" and "name" where "id" is an integer and "name" is a string.

A and B object schemas :


{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "A Object",

  "type": "object",
  "required": ["id", "name"],

  "properties": {
    "id": {
      "type": "integer"
    },
    "name": {
      "type": "string",
      "enum": ["A"]
    }
  },
  "additionalProperties": false
}

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "B Object",

  "type": "object",
  "required": ["id", "name"],

  "properties": {
    "id": {
      "type": "integer"
    },
    "name": {
      "type": "string",
      "enum": ["B"]
    }
  },
  "additionalProperties": false
}

A and B objects differ from C objects in that there name value is enforced. The name value of an A object must be a value contained in the field enum, where enum contains a single value.

My most complete schema to date is :


{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "To Date Solution",
  "description": "So far this is the most complete attempt at enforcing values to be contained within a JSON structure using JSON schemas.",

  "type": "array"
  "items": {
    "allOf": [
      {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "title": "C Object",

        "type": "object",
        "required": ["id", "name"],

        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    ]
  }
}

This enforces that all objects contained within must be of type C, which A and B are, though I am unsure how to enforce that at least single instance of A and B are contained within my array.

cloudfeet
  • 12,156
  • 1
  • 56
  • 57
Dawson
  • 4,391
  • 2
  • 24
  • 33

2 Answers2

14

What you are looking for is called "tuple typing".

If the value of the items keyword is an array, then the items in the array data must match up with the schema in the corresponding position. Additional items (past the last index) are matched by additionalItems (or are disallowed if additionalItems is false).

So, roughly what you want is something like:

{
    "type": "array",
    "items": [
        {"$ref": "#/definitions/itemTypeA"},
        {"$ref": "#/definitions/itemTypeB"}
    ],
    "additionalItems": {"$ref": "#/definitions/itemTypeC"},
    "definitions": {
        ... actual definitions for A/B/C ...
    }
}

If you want to ensure that A and B exist, then you simply specify a minimum length using minItems, so there are at least two items (which because of "tuple typing", must match up with A and B).

(This also assumes that A and B are the first items in the array. If that's not what you want, then it gets a bit more complicated - although there is a contains keyword proposed for v5 that would handle that neatly.)

Slightly more detailed live demo:

cloudfeet
  • 12,156
  • 1
  • 56
  • 57
  • This matches the answer I came to below and is what I implemented to move forward though I am looking for it to be position independent. Than you though. From this I gained a knowledge of tuple typing and the thought to look at Json Schema v5 :) – Dawson Nov 07 '13 at 17:49
  • 2
    You *can* do [something rather horrible](https://gist.github.com/anonymous/7359001) in v4 if you want it to be position independent. However, I would generally not advise it, as it's hard to read/understand. – cloudfeet Nov 07 '13 at 18:00
  • 2
    It's also worth noting that with both the v4 hack and the v5 `contains` keyword, the constraint is "at **least** one" - and `additionalItems` does not apply. For v5, there is [a workaround](https://gist.github.com/anonymous/7359101) to specify exactly one. – cloudfeet Nov 07 '13 at 18:05
  • Could you link documentation for v5 please?I was unable to locate it. – Dawson Nov 07 '13 at 18:47
  • The v5 spec is actually still being written, although it's promised very soon. However, the various proposed keywords are already on the [GitHub wiki](https://github.com/json-schema/json-schema/wiki/v5-Proposals). They're not guaranteed to all make it, but [`contains`](https://github.com/json-schema/json-schema/wiki/contains-(v5-proposal)) is almost certainly in. – cloudfeet Nov 08 '13 at 09:05
  • For me, the "$ref"s wouldn't work. Finally, I figured out "$id" was set to something "/foo/bla/", which causes problems. If your refs don't work, check/remove your "$id"! – Christopher K. Jan 15 '21 at 16:59
1

I have determined a solution that solves my problem, enforcing that A and B are present within the array, though does so positionally, thus requiring the JSON object I am validating to be ordered in some manner.

Working Schema


{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "Order dependent solution",

  "type": "array",
  "items": [
    {
      "$schema": "http://json-schema.org/draft-04/schema#",
      "title": "A Object",

      "type": "object",
      "required": ["id", "name"],

      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string",
          "enum": ["A"]
        }
      },
      "additionalProperties": false
    },
    {
      "$schema": "http://json-schema.org/draft-04/schema#",
      "title": "B Object",

      "type": "object",
      "required": ["id", "name"],

      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string",
          "enum": ["B"]
        }
      },
      "additionalProperties": false
    }
  ],

  "additionalItems": {

    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "C Object",

    "type": "object",
    "required": ["id", "name"],

    "properties": {
      "id": {
        "type": "integer"
      },
      "name": {
        "type": "string"
      }
    },
    "additionalProperties": false  
  }

}

This JSON schema validates a JSON array containing a A object at index 0, a B object at index 1, and C objects composing all remaining elements. This solution is usable, and allows me to move forward with development, though a order independent solution would be prefered.

Any help is appreciated! :)

PS - These schemas are not optimized, and demonstrate redundancy, which I will remove in the final version by making use of the "id" and "$ref" keyword.

Dawson
  • 4,391
  • 2
  • 24
  • 33