3

I am using Draft-04 of JSON Schema. Is it possible to set dependencies based on the existence of a sub-property, and/or depend on a sub-property? Or am I forced to use allOf to manage these kinds of dependencies?

I have the following (you can play with it at https://repl.it/@neverendingqs/JsonSchemaNestedDependencies):

'use strict';

const Ajv = require('ajv');
const assert = require('chai').assert;

// Using ajv@5.5.1
const draft4 = require('ajv/lib/refs/json-schema-draft-04.json');

const schema = {
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "foo1": {
      "type": [ "object" ],
      "properties": {
        "bar1": { "type": "string" }
      }
    },
    "foo2": {
      "type": [ "object" ],
      "properties": {
        "bar2": { "type": "string" }
      }
    }
  },
  "dependencies": {
    "foo1": ["foo2"],
    
    // Is this possible?
    "foo1/bar1": ["foo2/bar2"]
  }
};

const schemaName = 'my-schema';

const ajv = new Ajv();
ajv.addMetaSchema(draft4);
ajv.addSchema(schema, schemaName);


assert.isTrue(
  ajv.validate(schemaName, {
    "foo1": { "bar1": "a" },
    "foo2": { "bar2": "c" }
  }),
  ajv.errorsText(ajv.errors, { dataVar: 'event' })
);

assert.isFalse(ajv.validate(schemaName, {
  "foo1": { "bar1": "a" }
}));

// Looking to cause this to pass
assert.isFalse(ajv.validate(schemaName, {
  "foo1": { "bar1": "a" },
  "foo2": {}
}));

I am looking for Draft-04 answers, but am also interested in answers using later specifications.

EDIT: Draft-04 refers to the specifications under http://json-schema.org/specification-links.html#draft-4. Specifically, I am using dependencies which is defined under the Validation specification (https://datatracker.ietf.org/doc/html/draft-fge-json-schema-validation-00)

Community
  • 1
  • 1
neverendingqs
  • 4,006
  • 3
  • 29
  • 57

2 Answers2

1

It would be nice if dependencies supported a JSON Pointer, but it doesn't. You have to solve this using implication. I've broken it down using definitions to help make it more clear what is happening.

First I define schemas for the cases we are checking for: /foo1/bar1 is present and /foo2/bar2 is present. With those two definitions, I use anyOf to say either /foo1/bar1 is not present, or /foo2/bar2 is required. In other words, /foo1/bar1 implies /foo2/bar2.

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "foo1": {
      "type": [ "object" ],
      "properties": {
        "bar1": { "type": "string" }
      }
    },
    "foo2": {
      "type": [ "object" ],
      "properties": {
        "bar2": { "type": "string" }
      }
    }
  },
  "allOf": [{ "$ref": "#/definitions/foo1-bar1-implies-foo2-bar2" }],
  "dependencies": {
    "foo1": ["foo2"]
  },
  "definitions": {
    "foo1-bar1-implies-foo2-bar2": {
      "anyOf": [
        { "not": { "$ref": "#/definitions/foo1-bar1" } },
        { "$ref": "#/definitions/foo2-bar2" }
      ]
    },
    "foo1-bar1": {
      "properties": {
        "foo1": { "required": ["bar1"] }
      },
      "required": ["foo1"]
    },
    "foo2-bar2": {
      "properties": {
        "foo2": { "required": ["bar2"] }
      },
      "required": ["foo2"]
    }
  }
}
Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
0

It's pretty tricky to achive this in draft 4! You can use required in draft 4 to make a property required in an object...

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "foo1": {
      "type": [ "object" ],
      "properties": {
        "bar1": { "type": "string" }
      }
    },
    "foo2": {
      "type": [ "object" ],
      "properties": {
        "bar2": { "type": "string" }
      },
      "required": [ "bar2" ]
    }
  }
}

I can't make this change re-run in repl.it, but I checked it against the schema you want to fail using https://www.jsonschemavalidator.net

For draft-7 (which is latest at the time of writing), you can use if, then, else, which might be more intuative, but I think you'd still need to use required to achive this, as you want the subschema in if to pass or fail. Keywords for Applying Subschemas Conditionally.

Relequestual
  • 11,631
  • 6
  • 47
  • 83
  • I tried `{ "foo1": {}, "foo2": {} }`. It passes for the schema in the question, but fails for the schema in your answer. (Is your answer to draft 4 basically "no"?) – neverendingqs Mar 15 '18 at 15:24
  • I didn't anticipate that was JSON you would want to pass. I don't think I fully understand your requirements. Could you maybe write some sudo logic to add to your question? `dependencies` isn't a thing in JSON Schema, so your use of it in your example schema has no meaning to me. – Relequestual Mar 15 '18 at 15:35
  • I have added an edit around `dependencies` and which specification it appears in. I don't have a good example I can post here yet that's different / anonymized enough from what I'm looking into. – neverendingqs Mar 15 '18 at 17:23
  • Sorry, it seems like in all the times I've read through JSON Schema, I've missed `dependencies`. Looks like other answers my be more what you're looking for. – Relequestual Mar 16 '18 at 09:39