How do I require one field or another or (one of two others) but not all of them?

user3486184 picture user3486184 · Jun 3, 2014 · Viewed 48.6k times · Source

I am having trouble coming up with a JSON schema that will validate if the JSON contains either:

  • one field only
  • another field only
  • (one of two other fields) only

but not to match when multiples of those are present.

In my case specifically, I want one of

  • copyAll
  • fileNames
  • matchesFiles and/or doesntMatchFiles

to validate but I don't want to accept when more than that is there.

Here's what I've got so far:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "required": [ "unrelatedA" ],
    "properties": {
    "unrelatedA": {
        "type": "string"
    },
    "fileNames": {
        "type": "array"
    },
    "copyAll": {
        "type": "boolean"
    },
    "matchesFiles": {
        "type": "array"
    },
    "doesntMatchFiles": {
        "type": "array"
        }
    },
    "oneOf": [
         {"required": ["copyAll"], "not":{"required":["matchesFiles"]}, "not":{"required":["doesntMatchFiles"]}, "not":{"required":["fileNames"]}},
         {"required": ["fileNames"], "not":{"required":["matchesFiles"]}, "not":{"required":["doesntMatchFiles"]}, "not":{"required":["copyAll"]}},
         {"anyOf": [
               {"required": ["matchesFiles"], "not":{"required":["copyAll"]}, "not":{"required":["fileNames"]}},
               {"required": ["doesntMatchFiles"], "not":{"required":["copyAll"]}, "not":{"required":["fileNames"]}}]}
    ]
} ;

This matches more than I want to. I want this to match all of the following:

{"copyAll": true, "unrelatedA":"xxx"}
{"fileNames": ["aab", "cab"], "unrelatedA":"xxx"}
{"matchesFiles": ["a*"], "unrelatedA":"xxx"}
{"doesntMatchFiles": ["a*"], "unrelatedA":"xxx"}
{"matchesFiles": ["a*"], "doesntMatchFiles": ["*b"], "unrelatedA":"xxx"}

but not to match:

{"copyAll": true, "matchesFiles":["a*"], "unrelatedA":"xxx"}
{"fileNames": ["a"], "matchesFiles":["a*"], "unrelatedA":"xxx"}
{"copyAll": true, "doesntMatchFiles": ["*b"], "matchesFiles":["a*"], "unrelatedA":"xxx"}
{"fileNames": ["a"], "matchesFiles":["a*"], "unrelatedA":"xxx"}
{"unrelatedA":"xxx"}

I'm guessing there's something obvious I'm missing - I'd like to know what it is.

Answer

jruizaranguren picture jruizaranguren · Sep 3, 2014

The problem is the "not" semantics. "not required" does not mean "inclusion forbidden". It just means that you don't have to add it in order to validate that schema.

However, you can use "oneOf" to satisfy your specification in a simpler way. Remember that it means that "just one of these schemas can validate". The following schema achieves the property switching you are attempting to solve:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "required": [
        "unrelatedA"
    ],
    "properties": {
        "unrelatedA": {
            "type": "string"
        },
        "fileNames": {
            "type": "array"
        },
        "copyAll": {
            "type": "boolean"
        },
        "matchesFiles": {
            "type": "array"
        },
        "doesntMatchFiles": {
            "type": "array"
        }
    },
    "oneOf": [
        {
            "required": [
                "copyAll"
            ]
        },
        {
            "required": [
                "fileNames"
            ]
        },
        {
            "anyOf": [
                {
                    "required": [
                        "matchesFiles"
                    ]
                },
                {
                    "required": [
                        "doesntMatchFiles"
                    ]
                }
            ]
        }
    ]
}