Recursive items in JSON schema

40 views Asked by At

I'm designing a JSON schema with a recursive element. This element is not at the root, but in a subschema:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "http://example.org/json-schema/struct.schema.json",
    "type": "array",
    "items": {
        "title": "Structure",
        "type": "object",
        "properties": {
            "id": {
                "title": "ID",
                "type": "string"
            },
            "label": {
                "title": "Label",
                "type": "string"
            },
            "children": {
                "$anchor": "children",
                "title": "Children",
                "type": "array",
                "items": {
                    "title": "Structure Item",
                    "type": "object",
                    "properties": {
                        "id": {
                            "title": "ID",
                            "type": "string"
                        },
                        "href": {
                            "title": "Reference",
                            "type": "string"
                        },
                        "label": {
                            "title": "Structure Item Label",
                            "type": "string"
                        },
                        "sort_label": {
                            "title": "Sort Label",
                            "type": "string"
                        },
                        "children": {"$ref": "children"}
                    }
                },
                "anyOf": [
                    {"required": ["id", "href"]},
                    {"required": ["id", "children"]}
                ]
            }
        },
        "required": ["id", "label", "children"]
    }
}

When I try to compile the schema, I either get an error (with an online validator), or get caught in a loop (wit python-fastjsonschema), due to the recursion in items.properties.children.

From the examples in https://json-schema.org/understanding-json-schema/structuring#recursion it is not clear to me what is allowed for recursion and what is not. Recursive $refs inside $defs are not allowed, but $refs to the root anchor are listed in a valid example. They all create a loop (that is indeed the purpose of recursion!). I thought that $ref to a non-root anchor would be OK but it's clearly not.

I also tried to use a non-named anchor as in "children": {"$ref": "#/items/properties/children"}, and the schema compiles, but throws an error on validation.

How shall I model this schema?

1

There are 1 answers

3
Jeremy Fiel On BEST ANSWER

You need to reference the anchor with a # symbol.

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "http://example.org/json-schema/struct.schema.json",
    "type": "array",
    "items": {
        "title": "Structure",
        "type": "object",
        "properties": {
            "id": {
                "title": "ID",
                "type": "string"
            },
            "label": {
                "title": "Label",
                "type": "string"
            },
            "children": {
                "$anchor": "children",
                "title": "Children",
                "type": "array",
                "items": {
                    "title": "Structure Item",
                    "type": "object",
                    "properties": {
                        "id": {
                            "title": "ID",
                            "type": "string"
                        },
                        "href": {
                            "title": "Reference",
                            "type": "string"
                        },
                        "label": {
                            "title": "Structure Item Label",
                            "type": "string"
                        },
                        "sort_label": {
                            "title": "Sort Label",
                            "type": "string"
                        },
                        "children": {"$ref": "#children"}
                    }
                },
                "anyOf": [
                    {"required": ["id", "href"]},
                    {"required": ["id", "children"]}
                ]
            }
        },
        "required": ["id", "label", "children"]
    }
}

This is a passing instance.

[
    {
        "id": "",
        "label": "",
        "children": [
            {
                "id": "",
                "children": []
            },
            {
                "id": "",
                "href": ""
            }
        ]
    }
]

You may want to reconsider your anyOf required constraints because at the moment with anyOf, they are not really required in the way I think you want them required. Try oneOf