0
votes

Based on the previous answer I built a scheme that would meet my requirements. The question and the answer to it can be seen here.

The resulting scheme:

{   
    "definitions": {},
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "required": [
        "virtual"
    ],
    "properties": {
        "virtual": {
            "type": "array",
            "items": {
                "type": "object",
                "required": [
                    "type",
                    "path",
                    "entity",
                    "nodes"
                ],
                "properties": {
                    "type": {
                        "type": "string"
                    },
                    "path": {
                        "type": "string"
                    },
                    "entity": {
                        "enum": ["pde", "topaz"]
                    }         
                },
                "anyOf": [
                    {
                        "properties": {
                            "entity": {"const": "pde"},
                            "nodes": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "title": "The Items Schema",
                                    "required": [
                                        "id",
                                        "type",
                                        "address",
                                        "nozzles"
                                    ],
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "type": {
                                            "type": "string"
                                        },
                                        "address": {
                                            "type": "integer"
                                        },
                                        "nozzles": {
                                            "type": "array",
                                            "items": {
                                                "type": "integer"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    {
                        "properties": {
                            "entity": {"const": "topaz"},
                            "nodes": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "required": [
                                        "uid",
                                        "utype",
                                        "uaddress",
                                        "unozzles"
                                    ],
                                    "properties": {
                                        "uid": {
                                            "type": "integer"
                                        },
                                        "utype": {                  
                                            "type": "string"
                                        },
                                        "uaddress": {
                                            "type": "string"
                                        },
                                        "unozzles": {
                                            "type": "boolean"
                                        }
                                    }
                                }
                            }
                        } 
                    }
                ]
            }
        }
    }
}

And JSON:

{
    "virtual": [
        {
            "type": "bus",
            "path": "VBUS1",
            "entity": "pde",
            "nodes": [
                {
                    "id": "vrt_1",
                    "type": "dispenser",
                    "address": 1,
                    "nozzles": [1, 2, 3]
                },
                {
                    "id": "vrt_2",
                    "type": "dispenser",
                    "address": 2,
                    "nozzles": [4, 5, 3]
                }
            ]
        },
        {
            "type": "bus",
            "path": "VBUS2",
            "entity": "topaz",
            "nodes": [          
                {
                    "uid": 1,
                    "utype": "dispenser",
                    "uaddress": "false",
                    "unozzles": true
                },
                {
                    "uid": 2,
                    "utype": "dispenser",
                    "uaddress": "true",
                    "unozzles": false
                }
            ]
        }
    ]
}

The following problem appeared. When type=bus JSON has path and entity fields. But, if type=io the path and entity fields are missing and the node field looks different from the above two.

Hence I need to have anyOf that would track the value of the type field and another anyOf that would work for type=bus.

I will try to explain more clearly. It is necessary to track the value of the type field and if it is equal to bus, then the path and entity fields appear. Depending on the value of entity, the node field has a certain structure (exactly what is written in the diagram above).

I tried to make a schema with nested anyOf:

{
    "definitions": {},
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "required": [
        "virtual"
    ],
    "properties": {
        "virtual": {
            "type": "array",
            "items": {
                "type": "object",
                "required": [
                    "type"
                ],
                "properties": {
                    "type": {
                        "enum": ["bus", "io"]
                    }
                },
                "anyOf": [
                    {

                        "properties": {
                            "type": {"const": "bus"},
                            "path": { "type": "string" },
                            "entity": { "enum": ["topaz", "pde"] }
                        },
                        "anyOf":[
                        {
                            "properties":{
                                "entity": {"const": "pde"},
                                "nodes": {
                                    "type": "array",
                                    "items": {
                                        "type": "object",
                                        "required": [
                                            "id",
                                            "type",
                                            "address",
                                            "nozzles"
                                        ],
                                        "properties": {
                                            "id": { "type": "string" },
                                            "type": { "type": "string" },
                                            "address": { "type": "integer" },
                                            "nozzles": {
                                                "type": "array",
                                                "items": { "type": "integer" }
                                            }
                                        }
                                    }
                                }
                            }
                        },
                        {
                            "entity": {"const": "topaz"},
                            "nodes": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "required": [
                                        "uid",
                                        "utype",
                                        "uaddress",
                                        "unozzles"
                                    ],
                                    "properties": {
                                        "uid": { "type": "integer" },
                                        "utype": { "type": "string" },
                                        "uaddress": { "type": "string" },
                                        "unozzles": { "type": "boolean" }
                                    }
                                }
                            }
                        }
                        ]
                    },
                    {
                        "properties": {
                            "type": {"const": "io"},
                            "nodes": {
                                "type": "array",
                                "items":{
                                    "type": "object",
                                    "required": [
                                        "num",
                                        "key",
                                        "title",
                                        "path"
                                    ],
                                    "properties": {
                                        "num": { "type": "integer" },
                                        "key": { "type": "integer" },
                                        "title": { "type": "string" },
                                        "path": { "type": "string" }
                                    }
                                }
                            }
                        }
                    }
                ]
            }
        }   
    }
} 

Example of checking the scheme on the site

But as expected, the scheme accepts even those schemes that should not be accepted.

An example of a non-valid schema. There is no required id field for type=bus, entity=pde. There is no mandatory uid field for type=bus, entity=topaz. It feels like the second nested anyOf is ignored.

{
  "virtual": [
    {
      "type": "bus",
      "path": "VBUS1",
      "entity": "pde",
      "nodes": [
        {
          "not_id": "vrt_1",
          "type": "dispenser",
          "address": 1,
          "nozzles": [
            1,
            2,
            3
          ]
        },
        {
          "id": "vrt_2",
          "type": "dispenser",
          "address": 2,
          "nozzles": [
            4,
            5,
            3
          ]
        }
      ]
    },
    {
      "type": "bus",
      "path": "VBUS2",
      "entity": "topaz",
      "nodes": [
        {
          "not_uid": 1,
          "utype": "dispenser",
          "uaddress": "false",
          "unozzles": true
        },
        {
          "uid": 2,
          "utype": "dispenser",
          "uaddress": "true",
          "unozzles": false
        }
      ]
    },
    {
      "type": "io",
      "nodes": [
        {
          "num": 1,
          "key": 123,
          "title": "123",
          "path": "123"
        }
      ]
    }
  ]
}

Check the validity of the incorrect JSON.

In this case, anyOf for the type field works as expected. I assume that this problem appeared again because of the wrong layout of the scheme, but I could not find information on the Internet on nested anyOf.

When trying to insert all checks into one anyOf everything works as intended.

{   
    "definitions": {},
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "required": [
        "virtual"
    ],
    "properties": {
        "virtual": {
            "type": "array",
            "items": {
                "type": "object",
                "required": [
                    "type"
                ],
                "properties": {
                    "type": { "enum": ["bus", "io"] }        
                },
                "anyOf": [
                    {
                        "properties": {
                            "type": {"const": "bus"},
                            "path": { "type": "string" },
                            "entity": {"const": "pde"},
                            "nodes": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "title": "The Items Schema",
                                    "required": [
                                        "id",
                                        "type",
                                        "address",
                                        "nozzles"
                                    ],
                                    "properties": {
                                        "id": { "type": "string" },
                                        "type": { "type": "string" },
                                        "address": { "type": "integer" },
                                        "nozzles": {
                                            "type": "array",
                                            "items": { "type": "integer" }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    {
                        "properties": {
                            "type": {"const": "bus"},
                            "path": { "type": "string" },
                            "entity": {"const": "topaz"},
                            "nodes": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "required": [
                                        "uid",
                                        "utype",
                                        "uaddress",
                                        "unozzles"
                                    ],
                                    "properties": {
                                        "uid": { "type": "integer" },
                                        "utype": { "type": "string" },
                                        "uaddress": { "type": "string" },
                                        "unozzles": { "type": "boolean" }
                                    }
                                }
                            }
                        } 
                    },
                    {
                        "properties": {
                            "type": {"const": "io"},
                            "nodes": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "required": [
                                        "num",
                                        "key",
                                        "title",
                                        "path"
                                    ],
                                    "properties": {
                                        "num": { "type": "integer" },
                                        "key": { "type": "integer" },
                                        "title": { "type": "string" },
                                        "path": { "type": "string" }
                                    }
                                }
                            }
                        } 
                    }
                ]
            }
        }
    }
}

But:

  1. This scheme looks quite dirty
  2. It's not exactly what I'd like to see

The JSON schema itself, for which you want to create a schema:

{
    "virtual": [
        {
            "type": "bus",
            "path": "VBUS1",
            "entity": "pde",
            "nodes": [
                {
                    "id": "vrt_1",
                    "type": "dispenser",
                    "address": 1,
                    "nozzles": [1, 2, 3]
                },
                {
                    "id": "vrt_2",
                    "type": "dispenser",
                    "address": 2,
                    "nozzles": [4, 5, 3]
                }
            ]
        },
        {
            "type": "bus",
            "path": "VBUS2",
            "entity": "topaz",
            "nodes": [          
                {
                    "uid": 1,
                    "utype": "dispenser",
                    "uaddress": "false",
                    "unozzles": true
                },
                {
                    "uid": 2,
                    "utype": "dispenser",
                    "uaddress": "true",
                    "unozzles": false
                }
            ]
        },
        {
            "type": "io",
            "nodes": [
                "num": 4,
                "key": 123456,
                "title": "io",
                "path": "default"
            ]
        }
    ]
}

Full JSON has a rather complex structure and only part of it is represented here. Hence I would like to understand how to structure such things correctly (understand the idea itself, and preferably see an example of the correct scheme. At least schematic).

So, to summarize. I need to understand how any Of can be implemented in one of the anyOf variants. Is it feasible? And, if so, where can I see examples and instructions for compiling such schemes? If not, is there any workaround?

2
You claim that one of the versions of the schema above allows some invalid values. Can you provide one of those values that it shouldn't allow?gregsdennis
Edited the questionEgor Vasilyev

2 Answers

1
votes

I think I've found a solution. However, if there are any comments or corrections-I will be glad to hear.

Just in case I give an example of the resulting scheme:

{   
    "definitions": {},
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "required": [
        "virtual"
    ],
    "properties": {
        "virtual": {
            "type": "array",
            "items": {
                "type": "object",
                "required": [
                    "type"
                ],
                "properties": {
                    "type": {
                        "enum": ["bus", "io"]
                    }
                },
                "anyOf": [
                    {       
                        "properties":{
                            "type": {"const": "bus"},                       
                        },
                        "anyOf":[
                            {
                                "properties":{
                                    "path": { "type": "string" },
                                    "entity": {"const": "pde"},
                                    "nodes": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "required": [
                                                "id",
                                                "type",
                                                "address",
                                                "nozzles"
                                            ],
                                            "properties": {
                                                "id": { "type": "string" },
                                                "type": { "type": "string" },
                                                "address": { "type": "integer" },
                                                "nozzles": {
                                                    "type": "array",
                                                    "items": { "type": "integer" }
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                            {
                                "properties":{
                                    "path": { "type": "string" },
                                    "entity": {"const": "topaz"},
                                    "nodes": {
                                        "type": "array",
                                        "items": {
                                            "type": "object",
                                            "required": [
                                                "uid",
                                                "utype",
                                                "uaddress",
                                                "unozzles"
                                            ],
                                            "properties": {
                                                "uid": { "type": "integer" },
                                                "utype": { "type": "string" },
                                                "uaddress": { "type": "string" },
                                                "unozzles": { "type": "boolean" }
                                            }
                                        }
                                    }
                                }
                            }
                        ]
                    },
                    {
                        "properties": {
                            "type": {"const": "io"},
                            "nodes": {
                                "type": "array",
                                "items":{
                                    "type": "object",
                                    "required": [
                                        "num",
                                        "key",
                                        "title",
                                        "path"
                                    ],
                                    "properties": {
                                        "num": { "type": "integer" },
                                        "key": { "type": "integer" },
                                        "title": { "type": "string" },
                                        "path": { "type": "string" }
                                    }
                                }
                            }
                        }
                    }
                ]
            }
        }   
    }
} 
0
votes

Although nested anyOf works just fine in JSON Schema, I don't think that's the best solution in this case. Flattening the anyOfs and using some definitions cleans up the schema tremendously making it easier to reason about.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["virtual"],
  "properties": {
    "virtual": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["type"],
        "anyOf": [
          { "$ref": "#/definitions/pdm" },
          { "$ref": "#/definitions/topaz" },
          { "$ref": "#/definitions/io" }
        ]
      }
    }
  },
  "definitions": {
    "pdm": {
      "properties":{
        "type": { "const": "bus" },
        "entity": { "const": "pde" },
        ... type specific properties ...
      }
    },
    "topaz": {
      "properties": {
        "type": { "const": "bus" },
        "entity": { "const": "topaz" },
        ... type specific properties ...
      }
    },
    "io": {
      "properties": {
        "type": { "const": "io" },
        ... type specific properties ...
      }
    }
  }
}

If you use if/then/else or the Implication Pattern rather than the Enum Pattern, you can get better error messaging, but with a more complex schema.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["virtual"],
  "properties": {
    "virtual": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["type"],
        "allOf": [
          { "$ref": "#/definitions/pdm" },
          { "$ref": "#/definitions/topaz" },
          { "$ref": "#/definitions/io" }
        ]
      }
    }
  },
  "definitions": {
    "pdm": {
      "if": {
        "properties":{
          "type": { "const": "bus" },
          "entity": { "const": "pde" }
        },
        "required": ["type", "entity"]
      },
      "then": {
        "properties": {
          ... type specific constraints ...
        }
      }
    },
    ... additional types ...
}