Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make anyOf a set of mutually exclusive properties except one

I have a legacy API I'm trying to define in a JSON Schema and the object has a weird structure where there are a set of 4 properties, any one of them is required, and 3 of them are mutually exclusive. The are a more than 30 shared optional properties after that as well, I'll note them as ....

e.g.,

{ "foo": "bar", "baz": 1234, ... }  // OK
{ "foo": "bar", "buzz": 1234, ... } // OK
{ "foo": "bar", "fizz": 1234, ... } // OK
{ "foo": 1234, ... }                // OK
{ "baz": 1234, ... }                // OK
{ ... }                             // NOT OK
{ "baz": 1234, "buzz": 1234, ... }  // NOT OK

I could do a oneOf but that doesn't allow foo to be present with the others, anyOf allows for baz,buzz, and fizz to be present with each other which is not possible.

I tried to define something like the following:

{
    "type": "object",
    "properties": {
        "foo": {"type": "string"},
        "baz": {"type": "number"},
        "buzz": {"type": "number"},
        "fizz": {"type": "number"}
    },
    "anyOf": [
        {"required": ["foo"]},
        {"required": [{"oneOf": [
                {"required": ["baz"]},
                {"required": ["buzz"]},
                {"required": ["fizz"]}
            ]}
        ]}            
    ]
}

and

{
    "type": "object",
    "properties": {
        "foo": {"type": "string"},
        "baz": {"type": "number"},
        "buzz": {"type": "number"},
        "fizz": {"type": "number"}
    },
    "anyOf": [
        {"required": ["foo"]},
        {"oneOf": [
                {"required": ["baz"]},
                {"required": ["buzz"]},
                {"required": ["fizz"]}
            ]
        }            
    ]
}

But that does not work and I just don't know enough about json schema yet to know if this possible.

like image 245
JaredMcAteer Avatar asked Jun 20 '14 14:06

JaredMcAteer


2 Answers

Interesting! There might be a neater solution, but here we go...

The "mutually exclusive" constraint can be expressed by banning pairwise combinations of the properties:

{
    "not": {
        "anyOf": [
            {"required": ["baz", "buzz"]},
            {"required": ["buzz", "fizz"]},
            {"required": ["fizz", "baz"]}
        ]
    }
}

The "at least one of" constraint can be expressed with anyOf:

{
    "anyOf": [
        {"required": ["foo"]},
        {"required": ["baz"]},
        {"required": ["buzz"]},
        {"required": ["fizz"]}
    }
}

If you just combine these two constraints into a single schema, then it should work:

{
    "not": {"anyOf": [...]},
    "anyOf": ...
}
like image 186
cloudfeet Avatar answered Nov 14 '22 05:11

cloudfeet


You can make properties mutually exclusive in JSON Schema using pairwise exclusions, but this leads to combinatorial explosion. That becomes a problem when you have many mutually exclusive properties.

A linear solution is of the form:

  • one of:
    • property a
    • property b
    • property c
    • not any of:
      • property a
      • property b
      • property c

This only pays off if you have many properties.

{ "oneOf": [
  { "required": ["baz"] },
  { "required": ["buzz"] },
  { "required": ["fizz"] },
  { "not":
    { "anyOf": [
      { "required": ["baz"] },
      { "required": ["buzz"] },
      { "required": ["fizz"] }
    ] }
  }
] }

Combine this with @cloudfeet's answer to get the answer to your specific question.

like image 10
hraban Avatar answered Nov 14 '22 05:11

hraban