json-schema-spec icon indicating copy to clipboard operation
json-schema-spec copied to clipboard

✨ Proposal: Add a Global `defaultAdditionalProperties` Toggle at the Root Level

Open dani2819 opened this issue 9 months ago • 2 comments

Describe the inspiration for your proposal

Currently, JSON Schema requires additionalProperties: false to be set explicitly on every object where I want to disallow extra properties. This gets repetitive when I want all objects in my schema (root and nested) to enforce this rule consistently. For example, I have a schema like this:

{
  'type': 'object',
  'additionalProperties': false,
  'properties': {
    'name': { 'type': 'string' },
    'details': {
      'type': 'object',
      'additionalProperties': false,
      'properties': {
        'age': { 'type': 'integer' },
        'city': { 'type': 'string' }
      }
    }
  }
}

I have to repeat additionalProperties: false for both the root object and the details object. In a larger schema with many nested objects, this repetition grows tedious and increases the chance of forgetting it somewhere. There’s no way to set this once globally and have it cascade to all objects, so I’m stuck either duplicating it or using $ref to a shared definition, which still requires manual application each time. A global setting would make this cleaner and align with the DRY.

Describe the proposal

Add a new root-level keyword, like defaultAdditionalProperties, that sets the default value for additionalProperties across all objects in the schema unless overridden. For example:

{
  '$schema': 'http://json-schema.org/draft-07/schema#',
  'defaultAdditionalProperties': false,
  'type': 'object',
  'properties': {
    'name': { 'type': 'string' },
    'details': {
      'type': 'object',
      'properties': {
        'age': { 'type': 'integer' }
      }
    }
  }
}

Benefits

  • Reduces redundancy and simplifies schemas.
  • Makes strict schemas easier to write and maintain.
  • Still allows flexibility with local overrides.
  • Aligns with other global defaults in JSON Schema (e.g., default for values).

Describe alternatives you've considered

  • Using $defs and $ref works but still requires explicit references per object, not a true global solution.
  • Preprocessing schemas externally is an option, but it’s outside the spec and not portable.

Additional context

No response

dani2819 avatar Feb 24 '25 08:02 dani2819

I see where you're coming from, but this violates a core principle of how JSON Schema works. Specifically, a schema can only "see" that which is inside of it; it can't see anything in the JSON structure above it. So any subschema would be unaware that your new defaultAdditionalProperties keyword exists in the root.

Let's work through your example:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "defaultAdditionalProperties": false,
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "details": {
      "type": "object",
      "properties": {
        "age": { "type": "integer" }
      }
    }
  }
}

Your intent is for the subschema at /properties/details to also have a restriction where there are no additional properties. However, the only data that this subschema has to work from is

{
  "type": "object",
  "properties": {
    "age": { "type": "integer" }
  }
}

It has no way of knowing there is a keyword at the root defining more constraints.


What happens with a $ref? Currently $ref is defined so that your example is equivalent to

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://example.test/root",
  "defaultAdditionalProperties": false,
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "details": { "$ref": "./details" }
  }
}

{
  "$id": "https://example.test/details",
  "type": "object",
  "properties": {
    "age": { "type": "integer" }
  }
}

The schema https://example.test/details is expected to operate completely independently. Does the defaultAdditionalProperties still apply to it? What if I reference it from two different places, one of which has defaultAdditionalProperties and the other doesn't?

{
  "$id": "https://example.test/foo",
  "type": "object",
  "properties": {
    "bar": { "$ref": "./bar" },
    "baz": { "$ref": "./baz" }
  }
}

{
  "$id": "https://example.test/bar",
  "type": "object",
  "defaultAdditionalProperties": false,
  "properties": {
    "person": { "$ref": "./person" }
  }
}

{
  "$id": "https://example.test/baz",
  "type": "object",
  "properties": {
    "person": { "$ref": "./person" }
  }
}

{
  "$id": "https://example.test/person",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer" }
  }
}

So evaluating data looks like this:

{
  "foo": {
    "name": "Steve",
    "age": 13,
    "location": "Auckland" // invalid
  },
  "bar": {
    "name": "Steve",
    "age": 13,
    "location": "Auckland" // valid
  }
}

Both /foo and /bar are defined to meet the requirements of https://example.test/person, but one is valid and the other isn't.

gregsdennis avatar Feb 24 '25 19:02 gregsdennis

I agree, I have seen the usage of this at every object level than global. Though having a way to do this globally can be nice to have configuration, and may be should be implemented at library level ( ex: similar to config like includeJsr303Annotations in jsonSchema2Pojo )

jhsenjaliya avatar Mar 26 '25 21:03 jhsenjaliya