Search code examples
recursionjsonschemajson-schema-validator

Recursive items in JSON schema


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?


Solution

  • 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