json-schema-org / json-schema-vocabularies

Experimental vocabularies under consideration for standardization
53 stars 9 forks source link

Support non-hierarchical dependencies #20

Open yhack opened 6 years ago

yhack commented 6 years ago

I'm very interested in using JSON Schema to model documents that have non-hierarchical intradependencies.

The dependency specification includes this statement:

If the dependency value is a subschema, and the dependency key is a property in the instance, the entire instance must validate against the dependency value.

In other words, if this object has the dependency key, then this object must validate against the subschema. The crucial functionality missing is the ability to conditionally apply a subschema to a different part of the document.

Notes:

The ability to model intradocument dependencies would be useful both for UI generation and consistency checking of JSON documents with non-hierarchical relationships. Given JSON Schema's existing tooling, extending it to support this use case should be relatively straightforward.

handrews commented 6 years ago

@yhack can you give some examples of this sort of dependency? My feeling is that if is more flexible than it might initially appear. As a simple example, if you want property "x" to be either a string on an integer based on the value of boolean property "y", then you would write something like:

{
  "type": "object",
  "required": ["x", "y"],
  "properties": {
    "y": {"type": "boolean"}
  },
  "if": {
    "properties": {
      "y": {"const": true}
    }
  },
  "then": {
    "properties": {
      "x": {"type": "string"}
    }
  },
  "else": {
    "properties": {
      "x": {"type": "integer"}
    }
  }
}

You may also find @awwright's data relationships proposal interesting: json-schema-org/json-schema-spec#549.

yhack commented 6 years ago

@handrews I'm working on getting you an example I can share. In the meantime, I'll build on your example. The use case occurs when interdependent data is nested in different branches of the JSON tree. Referring to your example, suppose "x" and "y" were each objects with properties "a" and "b" respectively. Suppose you want to assert something about "a" based on the value of "b". Technically you could accomplish this with an if then statement in the root object, but this would quickly get messy when you have many relations to express and a deeply nested structure. In other words, I'm proposing that an if statement in the "b" schema definition can then assert a schema on "a".

json-schema-org/json-schema-spec#549 is interesting, but both that and json-schema-org/json-schema-spec#51 are about importing data into the schema rather than applying schema to an arbitrary part of the JSON document.

yhack commented 5 years ago

Here's an example. Suppose we have the following JSON document describing a birthday party:

{
    "guests": ["John", "Sam"],
    "dessert": "cake"
}

We might validate guests and desserts as follows:

{
  "properties": {
    "guests": {
      "type": "array",
      "items": {
        "enum": ["John", "Sam", "Lucy"]
      }
    },
    "dessert": {
      "type": "string",
      "enum": ["cake", "ice cream", "brownies", "cookies"]
    }
  }
}

Now suppose our guests are rather picky. As a caring host we want to provide a dessert that every guest likes. Dessert likes are provided by the following table:

            John    Sam    Lucy
cake        X       X
ice cream   X       X      X
brownies    X     
cookies             X      X

As you can see, which desserts are valid depends on which guests attend. Validating this type of internal consistency using JSON Schema is currently not possible in a general and elegant manner.* I'd like to propose a simple extension to support this.

Proposal: $focus node

As presently designed, which part of a JSON document that a JSON sub schema validates is based on the position of the sub schema relative to its parent schema (and as modified by $ref). Let's call the part of the JSON document that a particular sub-schema validates the focus node. Now, if we introduce a key word, lets call it $focus, to change the focus node using a (Relative) JSON Pointer, we can perform the validation needed for our birthday party document as follows:

{
  "properties": {
    "guests": {
      "type": "array",
      "items": {
        "enum": ["John", "Sam", "Lucy"]
      },
      "allOf": [
        {
          "if": {
            "contains": {
              "const": "John"
            }
          },
          "then": {
            "$focus": "/dessert",
            "enum": ["cake", "ice cream", "brownies"]
          }
        },
        {
          "if": {
            "contains": {
              "const": "Sam"
            }
          },
          "then": {
            "$focus": "/dessert",
            "enum": ["cake", "ice cream", "cookies"]
          }
        },
        {
          "if": {
            "contains": {
              "const": "Lucy"
            }
          },
          "then": {
            "$focus": "/dessert",
            "enum": ["ice cream", "cookies"]
          }
        }
      ]
    },
    "dessert": {
      "type": "string",
      "enum": ["cake", "ice cream", "brownies", "cookies"]
    }
  }
}          

Given this schema, our JSON birthday party document is valid because both guests John and Sam like cake. If Lucy were a guest, however, it would fail validation because cake is not one of her likes.

*The above validation might be accomplished with If Then statements at the top level but this can become terribly messy with deeply nested non-local constraints as per my previous post.

Keyword Comparison

no keyword: The current schema instance being processed applies to the current JSON document instance being processed, or focus node. $ref: references JSON Schema. $ref applies the referenced schema to the focus node. $data: references a point in the JSON document relative to the focus node. The referenced value is incorporated into the schema at the point of reference. $focus: changes the focus node to a point in the JSON document relative to the (former) focus node. The current schema instance being processed applies to the new focus node.

Does this sound reasonable?

awwright commented 5 years ago

I'd like to see an example of how this is useful for structural validation. JSON Schema generally does not encode valid data ranges; that is to say, if the guest list changes, the schema should still remain the same. So first question is, do you have an example that isn't validating the consistency of the data?

yhack commented 5 years ago

do you have an example that isn't validating the consistency of the data?

You hit the nail on the head. The point of this proposal is to validate data consistency.

I'd like to see an example of how this is useful for structural validation.

With $focus it's possible to write a schema that requires the structure to change based on JSON data. I'm not sure what that would be useful for but trivially John could require a dessert that is not simply a string but a complex object of name and ingredients. Is that what you are asking for?

handrews commented 4 years ago

@yhack With draft 2019-09 out (and hopefully soon having more than one implementation- although we're still working on the test suite), I'm encouraging folks with complex special-case keyword requests to implement an extension vocabulary for them. This is for several reasons:

But mostly, it's that there are tons of requests for really complex things that not very many people ask for. We have to draw the line somewhere, and at this point there's a really, really high bar for keywords going into the Core and Validations specifications. This is exactly why we designed the vocabulary feature.

handrews commented 4 years ago

As noted above, this is probably more of an extension vocabulary thing. Without the $ prefix as that is reserved to core. This is also related to json-schema-org/json-schema-spec#855.

yashwanthkalva commented 3 years ago

Without this feature, how do you write a validation, if you want it fail if two integer type properties are same, the values to these 2 properties will be either provided as input or has defaults. Please let me know.