Skip to content
This repository has been archived by the owner on Nov 2, 2023. It is now read-only.

Explain why inheritance isn't the right model #148

Closed
handrews opened this issue Aug 27, 2017 · 8 comments
Closed

Explain why inheritance isn't the right model #148

handrews opened this issue Aug 27, 2017 · 8 comments

Comments

@handrews
Copy link
Contributor

Whenever we get to topic guides, here, @jdesrosiers wrote an excellent answer to the question of inheritance, ending with the great quote

Schemas are not describing objects, they are a collection of constraints. So, thinking in terms of inheritance doesn't work.

This would be a good basis for an explanatory article, I think.

@handrews
Copy link
Contributor Author

Might as well bring the comment over. The rest of this was not written by me:


JSON Schema doesn't support inheritance. You can combine schemas using boolean operations in ways that can look like inheritance, but if you think about it that way, you will confuse what is actually happening. You will have to get out of that mindset and think about defining your data a little differently.

Your example is a fairly easy one to express except for one thing. I'll start with the schemas and then explain what it's doing and what the limitations are.

{
  "id": "http://example.com/schemas/person",
  "type": "object",
  "properties": {
    "firstName": { "type": "string" },
    "lastName": { "type": "string" }
  },
  "required": ["firstName", "lastName"]
}
{
  "id": "http://example.com/schema/employee",
  "allOf": [{ "$ref": "http://example.com/schemas/person" }],
  "properties": {
    "company": { "type": "string" }
  },
  "required": ["company"]
}

In the Employee schema, we use allOf to say that all of the constraints that apply to Person also apply to Employee. When we do this, nothing overrides anything else. When there are properties that appear in both schemas, both apply.

{
  "type": "object",
  "allOf": [
    {
      "properties": {
        "foo": { "type": "integer", "minimum": 2 }
      }
    }
    {
      "properties": {
        "foo": { "type": "integer", "multipleOf": 2 }
      }
    }
  ]
}

In this example the property "foo" has a minimum of 2 and excludes odd numbers.

The limitation you have with this approach is that you can't use "additionalProperties": false. Imagine if I added that constraint to the Person schema. Because all keywords apply when combining schemas, there would be no JSON value that could ever be valid against that schema. The Person schema is asserting that there can be no properties other than "firstName" and "lastName", but the Employee schema is asserting that the "company" schema is required. Here is one way around the problem.

{
  "id": "http://example.com/schema/final-employee",
  "allOf": [
    { "$ref": "http://example.com/schemas/person" },
    { "$ref": "http://example.com/schemas/employee" }
  ],
  "properties": {
    "firstName": {},
    "lastName": {},
    "company": {}
  },
  "additionalProperties": false
}

The problem, of course, is that any time you add a property to either of these schemas, you need update FinalEmployee as well. That's why I advise that you always ignore additional properties rather than explicitly forbid them. It has some value, but not nearly enough to be worth the pain.

So, I hope that helped. Schemas are not describing objects, they are a collection of constraints. So, thinking in terms of inheritance doesn't work.

@mfilenko
Copy link

Another good explanation is given in the "Understanding JSON Schema" book:

It is important to note that the schemas listed in an allOf, anyOf or oneOf array know nothing of one another. While it might be surprising, allOf can not be used to “extend” a schema to add more details to it in the sense of object-oriented inheritance.

@PaulCezanneFMR
Copy link

What's a best practice if you are used to thinking in terms of inheritance? How should I approach this from a design perspective?

@handrews
Copy link
Contributor Author

@PaulCezanneFMR that's a good question. I think it depends on what you're trying to get out of JSON Schema.

The Validation vocabulary is not really ideal for code generation tools, for instance. In particular, mimicking strongly typed language behavior typically requires making everything required and setting "additionalProperties": false, which is the opposite of what you want to do to make something extensible and modular. We've proposed a separate vocabulary for that but I haven't had time to work with the person who produced an initial pass at it due to draft-07. I hope to get back to that soon.

Also, draft-08 is focusing on modularity and re-usability, so we may add features or develop recommendations to support something more like OO inheritance, although the topic is extremely divisive so I'm really not sure how that will play out.

@rsmckinney
Copy link

I agree JSON Schema is currently unsuitable as a clear means to define type inheritance, but the reasoning here is flawed. This statement in particular captures the general misconception presented here:

Schemas are not describing objects, they are a collection of constraints.

Well... not really. This is a gross oversimplification of what a JSON Schema really is and what it is used for. It is far more than a set of constraints. Essentially it serves three primary functions:

  1. Models the structure of data
  2. Verifies an instance of the model via constraints
  3. Documents the model

The focus here is on 1 -- a JSON Schema as a means to model structured data. Yes, it also provides constraints on the model, but that does not somehow excuse it from properly modeling inheritance -- it just doesn't provide a clear way to express it (yet).

That said, "allOf" can be used to model multiple inheritance effectively, albeit with some exceptions and ambiguities that should be settled in the specification. The problem, in my view, is that most tools attempting to reflect a schema, esp. in a static language such as Java, simply get it wrong (because it's hard).

Cheers.

@jdesrosiers
Copy link
Member

It's pretty bold to argue that the primary maintainer of the JSON Specification (@handrews) misunderstands "what JSON Schema really is and what it is used for". But, I've been known to make that argument as well, so 🤷‍♂️.

JSON Schema really has two primary functions: validation and hypermedia. The specification reflects this by the way it's divided into parts: core, validation, and hyper-schema. These are the only things JSON Schema is designed for.

People have attempted to use JSON Schema in many unsupported ways including data modeling, documentation, and form building. I believe that the misconception that data modeling is a primary function of JSON Schema comes from Open API who has co-opted JSON Schema for that purpose. JSON Schema is a poor enough fit for this purpose that Open API had to define their own custom version of JSON Schema for their purposes.

Using JSON Schema for something other than validation or hypermedia is not a bad thing, but it will always be trying to fit a square peg into a round hole. You're trying to extract additional meaning out of an unordered declarative bag of constraints.

That said, "allOf" can be used to model multiple inheritance effectively, albeit with some exceptions and ambiguities that should be settled in the specification.

The specification is not ambiguous in this respect. allOf doesn't fit an inheritance model of any kind because nothing ever overrides or merges anything. allOf means that all of the schemas in the array must be valid for the allOf to be valid. That definition is simply not consistent with an inheritance model.

However, there is hope for those who want to use JSON Schema for things other than validation or hypermedia. A new JSON Schema concept called "vocabularies" is in the works. Vocabularies will (in theory) allow the definition of new keyword sets that are designed for things like data modeling, documentation, form building, or whatever else people want to use it for.

Finally, I suggest taking this conversation to the JSON Schema slack. It's a better forum for this kind of conversation and you'll get more people to weigh in on whether they are for or against your point of view.

@rsmckinney
Copy link

Thanks for the info. Maybe I'll move this to slack as you suggest, thanks.

@benjagm
Copy link
Contributor

benjagm commented Oct 11, 2023

@benjagm benjagm closed this as completed Oct 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants