JSON Schema conditional: require and not require

2020-06-19 05:04发布

问题:

I'm trying to implement this condition: if a particular property exists, then another property is required; but if it does not exist, another one is not required.

Also, in JSON schemas, can we use not in dependencies?

Here is a sample schema

var schema = {
    "properties": {
        "smaller": {
            "type": "number"
        },
        "larger": { "type": "number" },
        "medium":{'type':'string'},
        "bulky":{'type':'string'}
    },
    require:['smaller','larger'],
    additionalProperties:false
};

If "medium" is present, then "bulky" is required. Otherwise, "bulky" is not required.

Here "not required" means that if "medium" doesn't exist, then bulky must not be present.

回答1:

There are several ways to achieve required effect even not using JSON Schema draft-07 if-then-else.

logical operator and implication (draft-04 and above)

A logical implication here: if "medium" present then "bulky" is required can be translated to "medium" not present OR "bulky" is "required" (the latter implicates "medium" is present) which can be further elaborated to "medium" not required OR "bulky" is "required" (since if "medium" is present, it will satisfy condition of being required). See below schema:

"properties": {
  "smaller": {"type": "number"},
  "larger": { "type": "number" },
  "medium":{"type":"string"},
  "bulky":{"type":"string"}
},
"required":["smaller","larger"],
"anyOf" : [ 
  { 
    "not" : { "required" : ["medium"] }
  },
  {
    "required" : ["bulky"]
  }
],
"additionalProperties" : false

Check here for reference:

JSON schema - valid if object does *not* contain a particular property

http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.7

"anyOf" - logical OR, "oneOf" - XOR, "allOf" - AND, "not" - negation, yet pay attention to spec:

An instance is valid against this keyword if it fails to validate successfully against the schema defined by this keyword.

draft-06 - dependencies + propertyNames

Most obvious. I am not sure if you excluded this one in your question, so putting here just in case. Please note, that instead of "additionalProperties", if you wan't simply to limit valid keys, "propertyNames" could be used (and is actually what it was added for).

"properties": {
  "smaller": {"type": "number"},
  "larger": { "type": "number" },
  "medium":{"type":"string"},
  "bulky":{"type":"string"}
},
"required":["smaller","larger"],
"dependencies" : {
  "medium" : ["bulky"]
},
"propertyNames" : {
  "enum" : [
    "smaller",
    "larger",
    "medium",
    "bulky"
  ]
}

Check here for reference: http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.7

Update

After clarification in comment:

for draft-6 - here "not require" means that if "medium" dont exist then bulky " must not be present"

"must not" means preventing bulky being present.

I will rephrase your condition:

1. if "medium" exists "bulky" must be present -> both keys must be present at the same time

2. if "medium" does not exist "bulky" must not be present as well -> both keys must not be present at the same time

Can "bulky" exist and "medium" does not exist?

No. See 2. And vice versa (see 1.). Boolean equality (complementary to logical XOR).

Thus if "bulky" exists - it means "medium" must be always there... It implies that both are required or both must not be required (or even allowed).

Since it's draft-06, you can use also "propertyNames" for defining allowed property names (kind of shortcut to this logic).

logical operator and implication (draft-06 and above)

The proper logical operation translated to JSOn Schema would look like:

"oneOf" : [
  { "required" : ["medium","bulky"] }, <== this schema is satisfied if both keys appear in validated instance
  {
    "allOf" : [   <== !medium ^ !bulky - due to how "not" works in schema context
      {"not" : { "required" : ["medium"] } },  
      {"not" : { "required" : ["bulky"] } },
    ]
  }
]

An XOR - EITHER (both required) OR (medium not required AND bulky not required).

Please note I am not doing "not" : { "required" : ["medium","bulky"] } as when just one of those keys is present, "required" schema would fail which would mean "not" would return successfull validation result. One needs to rephrase it using de Morgans laws:

"oneOf" : [
  { "required" : ["medium","bulky"] },
  {
    "not" : {   <=== !medium ^ !bulky = !(medium v bulky)
      "anyOf" : [
        { "required" : ["medium"] },
        { "required" : ["bulky"]  },
      ]
    }
  }
]

However using "propertyNames" will also do the trick. See following schema:

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "properties": {
    "smaller": {"type": "number"},
    "larger": { "type": "number" },
    "medium":{"type":"string"},
    "bulky":{"type":"string"}
  },
  "required":["smaller","larger"],
  "anyOf" : [ 
    { 
       "required" : ["medium","bulky"]
    },
    {
      "propertyNames" : {
        "enum" : [
          "smaller",
          "larger"
        ]
      },
    }
  ],
  "examples" : [
    {
      "smaller" : 1,
      "larger" : 2,


    },
    {
      "smaller" : 1,
      "larger" : 2,
      "bulky" : "test",
      "medium" : ""
    },
    {
      "smaller" : 1,
      "larger" : 2,

      "medium" : ""
    },
    {
      "smaller" : 1,
      "larger" : 2,
      "bulky" : "test",

    },
  ]
}

Does it answer your question?



回答2:

JSON Schema Draft-07 has included these new keywords if, then and else which allow you to have conditional schemas.

In this example:

  • Only the foo property is required
  • However if foo is set to "bar" then the bar property also becomes required

var ajv = new Ajv({
  allErrors: true
});

var schema = {
  "properties": {
    "foo": {
      "type": "string"
    },
    "bar": {
      "type": "string"
    },

  },
  "required": ["foo"],
  "if": {
    "properties": {
      "foo": {
        "enum": ["bar"]
      }
    }
  },
  "then": {
    "required": ["bar"]
  }
}

var validate = ajv.compile(schema);

test({
  "foo": "bar",
  "bar": "baz"
}); // VALID

test({
  "foo": "xyz"
}); // VALID

test({
  "foo": "bar",
}); // NOT VALID


function test(data) {
  var valid = validate(data);
  if (valid) console.log('VALID', data);
  else console.log('NOT VALID', data);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/6.5.5/ajv.min.js"></script>

Hopefully this makes sense and you can adapt your code accordingly.

PS: in your schema you have the require property which I'm not sure is a valid JSON Schema keyword. You probably meant required instead.



回答3:

if you have multiple properties that dependent on respective values, you can use property dependencies.

{
  "type": "object",
  "properties": {
    "weight_1": {
        "type": "integer"
    },
    "weight_2": {
        "type": "integer"
    },
    "description_1": {
        "type": "string" 
    },
    "description_2": {
        "type": "string" 
    }
  },
  "allOf": [
    {
        "if": {
          "properties": {
            "weight_1": {
                "minimum": 10 
            }
          }
        },
        "then": {
          "dependencies": {
            "weight_1": ["description_1"]
          }
        }
    },
    {
        "if": {
          "properties": {
            "weight_2": {
                "minimum": 100
            }
          }
        },
        "then": {
          "dependencies": {
            "weight_2": ["description_2"]
          }
        }
    }
  ]
}


标签: jsonschema