Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursive (Circular) model $ref doesn't dereference unless wrapped in anyOf (sibling keys alongside $ref keys) #370

Open
iggyfisk opened this issue Feb 4, 2025 · 0 comments

Comments

@iggyfisk
Copy link

iggyfisk commented Feb 4, 2025

Pydantic recently released an update changing their JSON schema generator.

Previously the generated schema looked like

{
    "$defs": {
        "RecursiveModel": {
            "additionalProperties": false,
            "properties": {
                "value": {
                    "description": "A string",
                    "type": "string"
                },
                "children": {
                    "description": "Children with strings",
                    "items": {
                        "$ref": "#/$defs/RecursiveModel"
                    },
                    "type": "array"
                }
            },
            "required": [
                "value",
                "children"
            ],
            "title": "RecursiveModel",
            "type": "object"
        }
    },
    "allOf": [
        {
            "$ref": "#/$defs/RecursiveModel"
        }
    ]
}

which dereferences properly with json-schema-ref-parser. However, now the generated schema looks like

{
    "$defs": {
        "RecursiveModel": {
            "additionalProperties": false,
            "properties": {
                "value": {
                    "description": "A string",
                    "type": "string"
                },
                "children": {
                    "description": "Children with strings",
                    "items": {
                        "$ref": "#/$defs/RecursiveModel"
                    },
                    "type": "array"
                }
            },
            "required": [
                "value",
                "children"
            ],
            "title": "RecursiveModel",
            "type": "object"
        }
    },
    "$ref": "#/$defs/RecursiveModel"
}

which results in an unresolved '$ref': '#/%24defs/RecursiveModel/properties/children/items' after $RefParser.dereference(). As far as I can tell the newer schema should be valid.

Here's a full script showing the difference, and unresolved $ref

import $RefParser from "@apidevtools/json-schema-ref-parser";

async function main() {
  console.log("----OLD----");
  const old_dereferenced = await $RefParser.dereference({
    "$defs": {
      "RecursiveModel": {
        "additionalProperties": false,
        "properties": {
          "value": {
            "description": "A string",
            "type": "string",
          },
          "children": {
            "description": "Children with strings",
            "items": {
              "$ref": "#/$defs/RecursiveModel",
            },
            "type": "array",
          },
        },
        "required": ["value", "children"],
        "title": "RecursiveModel",
        "type": "object",
      },
    },
    "allOf": [
      {
        "$ref": "#/$defs/RecursiveModel",
      },
    ],
  });
  console.log(old_dereferenced["$defs"]["RecursiveModel"]["properties"]);

  console.log("----NEW----");
  const new_dereferenced = await $RefParser.dereference({
    "$defs": {
      "RecursiveModel": {
        "additionalProperties": false,
        "properties": {
          "value": {
            "description": "A string",
            "type": "string",
          },
          "children": {
            "description": "Children with strings",
            "items": {
              "$ref": "#/$defs/RecursiveModel",
            },
            "type": "array",
          },
        },
        "required": ["value", "children"],
        "title": "RecursiveModel",
        "type": "object",
      },
    },
    "$ref": "#/$defs/RecursiveModel",
  });
  console.log(new_dereferenced["$defs"]["RecursiveModel"]["properties"]);
}

main();

I get the output

----OLD----
<ref *1> {
  value: { description: 'A string', type: 'string' },
  children: {
    description: 'Children with strings',
    items: {
      additionalProperties: false,
      properties: [Circular *1],
      required: [Array],
      title: 'RecursiveModel',
      type: 'object'
    },
    type: 'array'
  }
}
----NEW----
{
  value: { description: 'A string', type: 'string' },
  children: {
    description: 'Children with strings',
    items: {
      '$defs': [Object],
      '$ref': '#/%24defs/RecursiveModel/properties/children/items'
    },
    type: 'array'
  }
}

Is this expected? Should I be using some different option?

@iggyfisk iggyfisk changed the title Recursive model $ref doesn't dereference unless wrapped in anyOf (sibling keys alongside $ref keys) Recursive (Circular) model $ref doesn't dereference unless wrapped in anyOf (sibling keys alongside $ref keys) Feb 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant