0
votes

I'm trying to create a JSON schema to validate YAML for some VSCode intellisense. What I'm trying to do is choose the correct subschema to use for a property in the main schema based on an adjacent key's value.

Some JSON examples:

[
  {
    "name": "doesntmatter",
    "matchMe": "stringToMatch:123whatever",
    "mergeMe": {
        "key1": "value1",
        "key2": "value2"
    }
  }
]
[
  {
    "name": "doesntmatter",
    "matchMe": "anotherStringToMatch:123whatever",
    "mergeMe": {
        "anotherKey": "valueSomething",
        "anotherKey2": "cheese"
    }
  }
]

So I need to choose the correct schemas for the mergeMe objects based on the substring match of matchMe. After following a bunch of answers, I'm at a point where I can either make it match multiple, and error my linter, or match none, but an online validator says it's ok (except nothing matches as the required fields aren't triggering).

I moved my sub-schemas to be merged into definitions to reference them, and then used an if/then to match. That worked with one, but then I tried to expand it to do the tree matching, and I can't get that to work. Someone said that I should wrap my if/thens in an allOf (I'm not sure why that would work since surely not all of them would match?). Changing it to an anyOf makes none of them match and I get no intellisense. Nor do I really understand why I should wrap single if/thens or thens in allOfs.

The idea is that based on the pattern it uses a definitions schema to match the mergeMe property, but the conditional logic isn't quite right. Thinned schema below:

{
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "http://example.com/example.json",
    "type": "array",
    "title": "The root schema",
    "description": "The root schema comprises the entire JSON document.",
    "default": [],
    "additionalItems": true,
    "definitions": {
        "stringToMatch": {
            "$id": "#/definitions/stringToMatch",
            "type": "object",
            "properties": {
                "key1": {
                    "type": "string"
                }
            },
            "required": [
                "key1"
            ],
            "additionalProperties": true
        },
        "anotherStringToMatch": {
            "$id": "#/definitions/anotherStringToMatch",
            "type": "object",
            "properties": {
                "key2": {
                    "type": "string"
                }
            },
            "required": [
                "key2"
            ],
            "additionalProperties": true
        }
    },
    "items": {
        "$id": "#/items",
        "type": "object",
        "title": "main schema",
        "description": "An explanation about the purpose of this instance.",
        "default": {},
        "examples": [],
        "required": [
            "name",
            "matchMe",
            "mergeMe"
        ],
        "properties": {
            "name": {
                "$id": "#/items/name",
                "type": "string",
                "title": "The name schema",
                "description": "An explanation about the purpose of this instance.",
                "default": "",
                "examples": []
            },
            "matchMe": {
                "$id": "#/items/matchMe",
                "type": "string",
                "title": "The matchMe schema",
                "description": "An explanation about the purpose of this instance.",
                "default": "",
                "examples": []
            }
        },
        "allOf": [
            {
                "if": {
                    "properties": {
                        "matchMe": {
                            "pattern": "^stringToMatch:[0-9.]+"
                        }
                    }
                },
                "then": {
                    "allOf": [
                        {
                            "type": "object",
                            "properties": {
                                "mergeMe": {
                                    "$ref": "#/definitions/stringToMatch"
                                }
                            }
                        }
                    ]
                }
            },
            {
                "if": {
                    "properties": {
                        "gear": {
                            "pattern": "^anotherStringToMatch:[0-9.]+"
                        }
                    }
                },
                "then": {
                    "allOf": [
                        {
                            "type": "object",
                            "properties": {
                                "mergeMe": {
                                    "$ref": "#/definitions/anotherStringToMatch"
                                }
                            }
                        }
                    ]
                }
            }
        ],
        "additionalProperties": true
    }
}

What I want in JS would look something like

const schema = { name, matchMe }
if (matchMe == "string1") schema.mergeMe = ...subschema1;
else if (...)
else if (...)

but I just can't really work it out. Can someone help?

Edit: jsonschema.dev playground - the idea being if I specify the food as prefixed by "fruit" I have to give it "pips" and "berry", whereas if I specify "vegetable" I have to give it a totally differet schema, and they don't overlap. https://jsonschema.dev/s/pHzGo

1
It’s really hard to reason your schema and instance when you use non-real values. Please can you replace your pretend values with real values? I know that seems silly but it provides context that might be helpful. Bonus points if you can provide a few jsonschema.dev links showing your problem.Relequestual
Sure sure - totally understandable. I've tried to create a small example using fruit and veg (edited at the bottom of the question).kingtaco
Could you provide some examples of instances that are not validating as expected. You're on the right track. I'm guessing you just have a few edge cases to smooth out.Jason Desrosiers
Currently none of the examples validate as expected, I'm afraid - they're a mix of what I had at the time of posting and what I have at time of editing in the jsonschema.dev link, with what should be validated for a schema that is incorrect. I've discovered that the "pattern" condition in the "if" isn't actually matching, having subbed the ref out for simple "required"s to see more clearly what's going on.kingtaco
I'm actually not entierly sure this is a json-schema question anymore - when brought down to a basic level of "if type is string" then () else require something - it clearly validates ok in online validators, but VSCode simply refuses to validate a property in an "if" block, although it can then require the same property to be a different type in the next "else" block.kingtaco

1 Answers

1
votes

This actually ended up being a bug in the VSCode YAML extension that was ingesting my schema, causing the if blocks to not evaluate, and has been raised, fixed and released.