3

My JSON data model has an object with a set of properties from which only 1 or none may be present as well other properties with their own constraints. Is there a way to do this without repetition as in this example?

Below is a simple example of as close as I have gotten to achieve this using Node.js + ajv.

var Ajv = require('ajv'),
ajv = new Ajv();

var schema = {
    type: 'object',
    properties: {
        id: {type: 'string'},
        a: {type: 'integer'},
        b: {type: 'integer'}
    },
    required: ['id'],
    oneOf: [
        {required: ['a']},
        {required: ['b']}
    ],
    additionalProperties: false
};

// invalid
var json1 = {
    id: 'someID',
    a: 1,
    b: 3
};

// valid
var json2 = {
    id: 'someID',
    b: 3
};

// valid
var json3 = {
    id: 'someID',
    a: 1
};

// valid
var json4 = {
    id: 'someID'
};

var validate = ajv.compile(schema);

console.log(validate(json1)); // false
console.log(validate(json2)); // true
console.log(validate(json3)); // true
console.log(validate(json4)); // false
Community
  • 1
  • 1
Severin
  • 35
  • 1
  • 7

1 Answers1

7

You have to think about the problem in a different way. Instead of trying to express what the schema can be, try expressing what the schema can't be. Here are two options.

One way is to say, "a" and "b" can not both be present.

{
  "type": "object",
  "properties": {
    "id": { "type": "string" },
    "a": { "type": "integer" },
    "b": { "type": "integer" }
  },
  "required": ["id"],
  "not": { "required": ["a", "b"] },
  "additionalProperties": false
}

Another way to frame the question is that if "a" is present, then "b" can not be present. And, if "b" is present, then "a" can not be present.

{
  "type": "object",
  "properties": {
    "id": { "type": "string" },
    "a": { "type": "integer" },
    "b": { "type": "integer" }
  },
  "required": ["id"],
  "dependencies": {
    "a": { "not": { "required": ["b"] } },
    "b": { "not": { "required": ["a"] } }
  },
  "additionalProperties": false
}
Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
  • Thank you Jason. Using dependencies is what I needed, as in your second example, but without using required. e.g. "a": { "not": ["b"] }, "b": { "not": ["a"] } – Severin Aug 06 '16 at 22:05
  • Actually, this did not work, since not keyword's value must be an object. How can I say, using dependencies, "if 'a' is present, 'b' cannot be present and vice versa" – Severin Aug 08 '16 at 10:51
  • I'm not sure what you are trying to solve. Both of the schemas I gave work correctly as given. I'd need more information on what exactly isn't working for you. – Jason Desrosiers Aug 09 '16 at 00:00
  • You're right, the schema you gave does work correctly and have tested it properly now too. I was confused about the use of required in the dependencies, because I understood it as: "If 'a' is present, then 'b' is not required, but may be present" rather than "If 'a' is present, then 'b' cannot be present" which is how it actually works – Severin Aug 09 '16 at 11:52
  • First part of answer is excellent and concise for an optional element that can only have one in a list of sub-elements. – Darrell Teague Apr 15 '20 at 21:43