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

$ref inheritance not supported #613

Open
JBBianchi opened this issue Jul 25, 2024 · 9 comments
Open

$ref inheritance not supported #613

JBBianchi opened this issue Jul 25, 2024 · 9 comments

Comments

@JBBianchi
Copy link

JBBianchi commented Jul 25, 2024

When modeling inheritance, we can use $ref to point to an existing definition and extend it.

e.g.:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Sample",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "neighborhood": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/person"
      }
    }
  },
  "$defs": {
    "baseType": {
      "title": "BaseType",
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        }
      }
    },
    "person": {
      "title": "Person",
      "$ref": "#/$defs/baseType",
      "type": "object",
      "unevaluatedProperties": false,
      "properties": {
        "age": {
          "type": "number"
        }
      }
    }
  }
}

and the expected output to be equivalent to:

export interface Sample {
  neighborhood?: Person[];
}
export interface Person extends BaseType {
  age?: "number";
}
export interface BaseType {
  name?: string;
}

But with the provided code, the output of json-schema-to-typescript (14.1) with default options will be erroneous:

  • There won't be any type generated for BaseType even if it's "reachable"
  • Properties from BaseType will be missing in Person
  • Person object can have extra properties even though unevaluatedProperties is set to false

Here is the generated output with the default settings:

export interface Sample {
  neighborhood?: Person[];
}
export interface Person {
  age?: "number";
  [k: string]: unknown;
}

Even by enabling unreachableDefinitions, the output isn't better. BaseType appears but it's still not used properly.

export interface Sample {
  neighborhood?: Person[];
}
export interface Person {
  age?: "number";
  [k: string]: unknown;
}
/**
 * This interface was referenced by `Sample`'s JSON-Schema
 * via the `definition` "baseType".
 */
export interface BaseType {
  name?: string;
  [k: string]: unknown;
}
/**
 * This interface was referenced by `Sample`'s JSON-Schema
 * via the `definition` "person".
 */
export interface Person1 {
  age?: "number";
  [k: string]: unknown;
}

The closest schema that would produce something close to the expected result would be:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Sample",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "neighborhood": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/person"
      }
    }
  },
  "$defs": {
    "baseType": {
      "title": "BaseType",
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        }
      }
    },
    "person": {
      "title": "Person",
      "type": "object",
      "allOf": [
        { "$ref": "#/$defs/baseType" },
        {
          "properties": {
            "age": {
              "type": "number"
            }
          },
          "unevaluatedProperties": false
        }
      ]
    }
  }
}

Edit: I removed a part I was talking about using additionalProperties to store the $ref but I was mistaken, it's not the same behavior.

bcherny added a commit that referenced this issue Aug 12, 2024
@petrosv91
Copy link

Any update on that? I have the same issue.

@noootch
Copy link

noootch commented Oct 6, 2024

+1 on this bug.

We are working with pydantic in our project and they recently removed their workaround for this bug so that now we have to rebuild this workaround as long as this bug is not solved.

Here is a comment that leads to the bugfix in the json schema.

Is it possible that this issue is due to the underlying ref parser? I found this discussion and it seems it won't be solved there. From my understanding to solve this bug the ref parser needs to be changed or a user setting has to be added that allows to switch the ref parser.

@bergamoticus
Copy link

Excuse me, how exactly should inheritance $ref be differentiated from reuse $ref
Is it that the first $ref is inheritance ref and the interface must be extended then?
Or is it all refs within allOf after "type": "object" ?

@JBBianchi
Copy link
Author

Excuse me, how exactly should inheritance $ref be differentiated from reuse $ref Is it that the first $ref is inheritance ref and the interface must be extended then? Or is it all refs within allOf after "type": "object" ?

Do you have more context about inheritance vs reuse ? I must admit I don't understand you question(s). Some samples maybe and expected output ?

@bergamoticus
Copy link

Sorry for sending unclear message. I tried to illustrated it with the example below:

@bergamoticus
Copy link

bergamoticus commented Oct 10, 2024

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "Function",
    "type": "object",
    "additionalProperties": false,
    "allOf": [
        {
            "$ref": "#/$defs/baseType"
        },
        {
            "properties": {
                "in": {
                    "$ref": "#/$defs/taggedValue"
                },
                "out": {
                    "$ref": "#/$defs/taggedValue"
                }
            }
        }
    ],
    "$defs": {
        "baseType": {
            "title": "BaseType",
            "type": "object",
            "properties": {
                "name": {
                    "type": "string"
                }
            }
        },
        "taggedValue": {
            "title": "TaggedValue",
            "type": "object",
            "properties": {
                "tag": {
                    "type": "string"
                },
                "value": {
                    "type": "number"
                }
            }
        }
    }
}

@bergamoticus
Copy link

Actual output:

export type Function = BaseType & {
  in?: TaggedValue;
  out?: TaggedValue;
  [k: string]: unknown;
};

export interface BaseType {
  name?: string;
  [k: string]: unknown;
}
export interface TaggedValue {
  tag?: string;
  value?: number;
  [k: string]: unknown;
}

@bergamoticus
Copy link

Desired output:

export interface Function extends BaseType  {
  in?: TaggedValue;
  out?: TaggedValue;
};

export interface BaseType {
  name?: string;
}
export interface TaggedValue {
  tag?: string;
  value?: number;
}

@bergamoticus
Copy link

In the above example $ref is intended to be used for
a) extending BaseType
b) shortcut/reuse TaggedValue for properties in and out. I think the correct term in UML is composition

My question is how to distinguish these different uses of ref? What is the criteria?

For instance, refs, under properties are for composition,
and refs used out of properties is for inheritance (and and interface must be extended)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants