JSON Schema - require all properties

28.6k views Asked by At

The required field in JSON Schema

JSON Schema features the properties, required and additionalProperties fields. For example,

{
    "type": "object",
    "properties": {
        "elephant": {"type": "string"},
        "giraffe": {"type": "string"},
        "polarBear": {"type": "string"}
    },
    "required": [
        "elephant",
        "giraffe",
        "polarBear"
    ],
    "additionalProperties": false
}

Will validate JSON objects like:

{
    "elephant": "Johnny",
    "giraffe": "Jimmy",
    "polarBear": "George"
}

But will fail if the list of properties is not exactly elephant, giraffe, polarBear.

The problem

I often copy-paste the list of properties to the list of required, and suffer from annoying bugs when the lists don't match due to typos and other silly errors.

Is there a shorter way to denote that all properties are required, without explicitly naming them?

7

There are 7 answers

7
San Jay On BEST ANSWER

You can just use the "minProperties" property instead of explicity naming all the fields.

{
    "type": "object",
    "properties": {
        "elephant": {"type": "string"},
        "giraffe": {"type": "string"},
        "polarBear": {"type": "string"}
    },
    "additionalProperties": false,
    "minProperties": 3
}
0
jruizaranguren On

I doubt there exists a way to specify required properties other than explicitly name them in required array.

But if you encounter this issue very often I would suggest you to write a small script that post-process your json-schema and add automatically the required array for all defined objects.

The script just need to traverse the json-schema tree, and at each level, if a "properties" keyword is found, add a "required" keyword with all defined keys contained in properties at the same level.

Let the machines do the bore stuff.

1
Eliav Louski On

if you using the library jsonschema in python use custom validators:

first create custom validator:

# Custom validator for requiring all properties listed in the instance to be in the 'required' list of the instance
def allRequired(validator, allRequired, instance, schema):
    if not validator.is_type(instance, "object"):
        return
    if allRequired and "required" in instance:
        # requiring all properties to 'required'
        instanceRequired = instance["required"]
        instanceProperties = list(instance["properties"].keys())
        for property in instanceProperties:
            if property not in instanceRequired:
                yield ValidationError("%r should be required but only the following are required: %r" % (property, instanceRequired))
        for property in instanceRequired:
            if property not in instanceProperties:
                yield ValidationError("%r should be in properties but only the following are properties: %r" % (property, instanceProperties))

then extend an exsitsing validator:

all_validators = dict(Draft4Validator.VALIDATORS)
all_validators['allRequired'] = allRequired

customValidator = jsonschema.validators.extend(
    validator=Draft4Validator,
    validators=all_validators
)

now test:

schema =  {"allRequired": True}
instance = {"properties": {"name": {"type": "string"}}, "required": []}
v = customValidator(schema)
errors = validateInstance(v, instance)

you will get the error: 'name' should be required but only the following are required: []

0
denisb411 On

You can use the function below:

export function addRequiredAttributeRecursive(schema) {
  if (schema.type === 'object') {
    schema.required = [];
    Object.keys(schema.properties).forEach((key) => {
      schema.required.push(key);
      if (schema.properties[key].type === 'object') {
        schema.properties[key] = addRequiredAttributeRecursive(
          schema.properties[key],
        );
      } else if (schema.properties[key].type === 'array') {
        schema.properties[key].items = addRequiredAttributeRecursive(
          schema.properties[key].items,
        );
      }
    });
  } else if (schema.type === 'array') {
    if (schema.items.type === 'object') {
      schema.items = addRequiredAttributeRecursive(schema.items);
    }
  }

  return schema;
}

It recursively write the required attribute for every property on all objects from the schema you have.

0
Sang On

If you are using Javascript, you can use property getter.

{
    "type": "object",
    "properties": {
        "elephant": {"type": "string"},
        "giraffe": {"type": "string"},
        "polarBear": {"type": "string"}
    },
    get required() { return Object.keys(this.properties) },
    "additionalProperties": false
}
0
Eyal Shulman On

As suggested by others, here's such post-processing python code:

def schema_to_strict(schema):
    if schema['type'] not in ['object', 'array']:
        return schema

    if schema['type'] == 'array':
        schema['items'] = schema_to_strict(schema['items'])
        return schema

    for k, v in schema['properties'].items():
        schema['properties'][k] = schema_to_strict(v)

    schema['required'] = list(schema['properties'].keys())
    schema['additionalProperties'] = False
    return schema
0
Dallas On

I do this in code with a one-liner, for instance, if I want to use required for insert in a DB, but only want to validate against the schema when performing an update.

prepareSchema(action) {
    const actionSchema = R.clone(schema)
    switch (action) {
        case 'insert':
            actionSchema.$id = `/${schema.$id}-Insert`
            actionSchema.required = Object.keys(schema.properties)
            return actionSchema
        default:
            return schema
    }
}