0

I have seen answers to similar questions that do not quite match this particular case, so apologies if I missed a relevant answer.

I have a heterogeneous array of objects that I would like to validate. These objects have the same format at the top level, but the child objects are quite different and can only be identified by the attributes present in each child.

The problem maps to validating the following data, though I have more than two object types in the array:

{
  "heterogeneous_array": [{
      "arbitrary_name": "foobar",
      "params": {
        "aa": "foo",
        "ab": "bar"
      }
    },
    {
      "arbitrary_name": "barfoo",
      "params": {
        "ba": "baz",
        "bb": "bot"
      }
    }
  ]
}

I am using the following schema, which claims to validate the input json even when the objects under the “params” key are invalid. How can I fix the json schema?

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "heterogeneous_array": {
      "$ref": "#/definitions/heterogeneous_array"
    }
  },
  "definitions": {
    "heterogeneous_array": {
      "type": "array",
      "items": {
        "arbitrary_name": {
          "type": "string"
        },
        "params": {
          "oneOf": [{
              "$ref": "#/definitions/schema_a"
            },
            {
              "$ref": "#/definitions/schema_b"
            }
          ]
        },
        "required": ["arbitrary_name", "params"]
      }
    },
    "schema_a": {
      "properties": {
        "aa": {
          "type": "string"
        },
        "ab": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "required": ["aa", "ab"]
    },
    "schema_b": {
      "properties": {
        "ba": {
          "type": "string"
        },
        "bb": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "required": ["ba", "bb"]
    }
  }
}

Thank you in advance!

Toaster
  • 1,911
  • 2
  • 23
  • 43

1 Answers1

3

The first thing that jumps out at me is that parameters and arbitrary_name are not JSON Schema keywords. I think you're missing a few properties keywords.

Try this:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "heterogeneous_array": {
      "$ref": "#/definitions/heterogeneous_array"
    }
  },
  "definitions": {
    "heterogeneous_array": {
      "type": "array",
      "items": {
        "properties": {             // missing this
          "arbitrary_name": {
            "type": "string"
          },
          "params": {
            "oneOf": [{
                "$ref": "#/definitions/schema_a"
              },
              {
                "$ref": "#/definitions/schema_b"
              }
            ]
          }
        },
        "required": ["arbitrary_name", "params"]    // "arbitrary_name" was "name"
      }
    },
    "schema_a": {
      "properties": {             // was "parameters"
        "aa": {
          "type": "string"
        },
        "ab": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "required": ["aa", "ab"]
    },
    "schema_b": {
      "properties": {             // was "parameters"
        "ba": {
          "type": "string"
        },
        "bb": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "required": ["ba", "bb"]
    }
  }
}

There are a few other things in there that I commented.

The last thing (to fix what you have) is minor, should be noted, and is probably supported by your JSON library anyway: boolean values in JSON are always lower-case (e.g. false not False). (They're actually defined as explicit tokens.)


What is unclear from your question is whether the foobar object requires the aa and ab parameters while the barfoo object requires the ba and bb parameters. If this is the case, you can do some other things if you're using JSON Schema draft 6 or higher.

Draft 6 defines a const property that you can use to isolate values for specific properties and enforce subschemas on other portions of the object. Using this, you can create a sort of switch statement.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "heterogeneous_array": {
      "$ref": "#/definitions/heterogeneous_array"
    }
  },
  "definitions": {
    "heterogeneous_array": {
      "type": "array",
      "items": {
        "oneOf": [
          {"$ref": "#/definitions/schema_a"},
          {"$ref": "#/definitions/schema_b"}
        ],
        "required": ["arbitrary_name", "params"]
      }
    },
    "schema_a": {
      "properties": {
        "arbitrary_name": {"const": "foobar"},
        "params": {
          "properties": {
            "aa": {
              "type": "string"
            },
            "ab": {
              "type": "string"
            }
          },
          "additionalProperties": false,
          "required": ["aa", "ab"]
        }
      }
    },
    "schema_b": {
      "properties": {
        "arbitrary_name": {"const": "barfoo"},
        "params": {
          "properties": {
            "ba": {
              "type": "string"
            },
            "bb": {
              "type": "string"
            }
          },
          "additionalProperties": false,
          "required": ["ba", "bb"]
        }
      }
    }
  }
}

This is a bit of a reorganization and you'll need a schema_? for each value of arbitrary_name that you have.

Further to this, if you're using draft 7, you also have the if/then/else keywords, but I don't think that makes this use case any cleaner.

gregsdennis
  • 7,218
  • 3
  • 38
  • 71
  • 1
    Thank you! Your comments were very helpful, I hadn't heard of the const property before. The False vs false was a good catch as well. Oddly enough I had a version with the properties tag included but I must have missed-configured it in another way. – Toaster Mar 14 '19 at 20:35