JaredMcAteer JaredMcAteer - 15 days ago 9
JSON Question

How to make anyOf a set of multually 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.

Answer

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": ...
}