bcherny / json-schema-to-typescript

Compile JSON Schema to TypeScript type declarations
https://bcherny.github.io/json-schema-to-typescript-browser/
MIT License
2.94k stars 391 forks source link

Improve output for top level $ref #132

Open dherges opened 6 years ago

dherges commented 6 years ago

A $ref breaks when used on the root object

Example A

Non-working example:

{
 "$schema": "http://json-schema.org/draft-04/schema#",
 "type": "object",
 "$ref": "#/definitions/anatomic-location",
  "definitions": {
    "anatomic-location": {
      "description": "thing",
      "properties": {
        /* .. */
        "children": {
          "type": "array",
          "items": { "$ref": "#/definitions/anatomic-location" }
        },
      }
    }
  }
}

Error message will be:

Refs should have been resolved by the resolver!

It will log:

  title: 'AnatomicLocation',
  properties: 
   { id: { type: 'integer' } },
  '$ref': '#' }

I wonder where the $ref: '#' comes from?

Example B

Working but with duplicated interface

  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "$ref": "#/definitions/anatomic-location/properties"
  },
  "definitions": {
    "anatomic-location": {
      "description": "thing...",
       "properties": { /* .. */ }
     }
  }
}

Generated code is:

export interface AnatomicLocation {
  id?: number;
  children?: AnatomicLocation1[];
}

export interface AnatomicLocation1 {
  id: number;
  children?: AnatomicLocation1[];
}
bcherny commented 6 years ago

Thanks for the reports @dherges.

@BigstickCarpet Is Example A kosher?

bcherny commented 6 years ago

@dherges Can you post the complete JSON-Schema for Example B? From what you gave it's not clear to me why the output is incorrect.

dherges commented 6 years ago

Hi @bcherny,

thanks for the response and your time! Here are the full repros.

Example A

{
  "title": "Example Schema",
  "definitions": {
    "person": {
      "type": "object",
      "properties": {
        "firstName": {
          "type": "string"
        },
        "children": {
          "type": "array",
          "items": { "$ref": "#/definitions/person" }
        }
      }
    }
  },
  "type": "object",
  "$ref": "#/definitions/person"
}

Error message:

$ node_modules/.bin/json2ts wound-ui/ui/foo.json 
error Refs should have been resolved by the resolver! { title: 'Example Schema',
  definitions: { person: { type: 'object', properties: [Object] } },
  type: 'object',
  required: [],
  additionalProperties: true,
  id: 'Foo',
  properties: 
   { firstName: { type: 'string' },
     children: { type: 'array', items: [Object] } },
  '$ref': '#' }

Example B

{
  "title": "Example Schema",
  "definitions": {
    "person": {
      "type": "object",
      "properties": {
        "firstName": {
          "type": "string"
        },
        "children": {
          "type": "array",
          "items": { "$ref": "#/definitions/person" }
        }
      }
    }
  },
  "type": "object",
  "properties": {
    "$ref": "#/definitions/person/properties"
  }
}
export interface ExampleSchema {
  firstName?: string;
  children?: Person[];
  [k: string]: any;
}
export interface Person {
  firstName?: string;
  children?: Person[];
  [k: string]: any;
}

I could live by that output as it's somewhat 'okay' due to duck typing in TypeScript world, but I wonder why two interfaces are being generated?!?

dherges commented 6 years ago

I found two working version by setting $ref: '.' and $ref: '#' in the properties.children.items:

{
  "type": "object",
  "properties": {
    "firstName": {
      "type": "string"
    },
    "children": {
      "type": "array",
      "items": { "$ref": "." }
   }
  }
}
export interface Bar {
  firstName?: string;
  children?: Bar[];
  [k: string]: any;
}

{
  "type": "object",
  "properties": {
    "firstName": {
      "type": "string"
    },
    "children": {
      "type": "array",
      "items": { "$ref": "#" }
   }
  }
}
export interface Bar {
  firstName?: string;
  children?: Bar[];
  [k: string]: any;
}
JamesMessinger commented 6 years ago

@bcherny - Example A in @dherges' post isn't supported by json-schema-ref-parser. It's technically not a valid JSON Schema, since a JSON Reference object is only allowed to have a $ref property (any other property are ignored, per the spec).

{
  "$ref": "#/foo/bar",
  "name": "Foo"               // <--- not allowed
}

That said, json-schema-ref-parser supports JSON Reference objects with additional properties, even though that's not technically spec-compliant. I chose to support it because many people asked for it and had real-world situations where they relied on it. However, allowing a non-spec-compliant feature causes problems such as the one that @dherges is facing.

My recommendation is to go with something like Example B in @dherges' post. Even though it may not seem quite as elegant, it is spec-compliant.

bcherny commented 6 years ago

@BigstickCarpet As always, thanks for chiming in.

@dherges You heard it here, Example A is invalid. For example B, nicer output might be:

export type ExampleSchema = Person
export interface Person {
  firstName?: string;
  children?: Person[];
  [k: string]: any;
}

Is that what you had in mind?

bcherny commented 6 years ago

@dherges Can you chime in?

dherges commented 6 years ago

Yes, exactly!

zoonman commented 3 years ago

I have the same issue. Here is a schema eksctl.json.zip

k2on commented 3 years ago

Any update on this?

ShivamJoker commented 2 years ago

I am stil getting this error any workarounds?

mmdk95 commented 2 years ago

As I ran into the same issue (and this is the top result in google), here is what you can do to prevent this error from occurring (not gonna join the debate on valid or invalid schema 😉 ).

As your$ref can most likely be replaced with an allOf, instead of doing something like this:

YAML JSON
```yaml $ref: "#/definitions/child" definitions: child: description: Hello World ``` ```json { "$ref": "#/definitions/child", "definitions": { "child": { "description": "Hello World", } } } ```

You can do this, and your types should generate

YAML JSON
```yaml allOf: - $ref: "#/definitions/child" definitions: child: description: Hello World ``` ```json { "allOf": [ { "$ref": "#/definitions/child" } ], "definitions": { "child": { "description": "Hello World", } } } ```
zanona commented 1 year ago

Just listing an example being used in production, I hope that's ok. https://turbo.build/schema.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/Schema",
  "definitions": {
    "Schema": {
      "type": "object",
      "properties": {...}
    }
  }
}
toteto commented 1 year ago

Facing the same issue for schema at: https://github.com/decentralized-identity/presentation-exchange/blob/main/schemas/v2.0.0/submission-requirement.json

submission-requirement.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Presentation Submission Requirement",
  "definitions": {
    "submission_requirement": {
      "type": "object",
      "oneOf": [
        {
          "properties": {
            "name": { "type": "string" },
            "purpose": { "type": "string" },
            "rule": {
              "type": "string",
              "enum": ["all", "pick"]
            },
            "count": { "type": "integer", "minimum": 1 },
            "min": { "type": "integer", "minimum": 0 },
            "max": { "type": "integer", "minimum": 0 },
            "from": { "type": "string" }
          },
          "required": ["rule", "from"],
          "additionalProperties": false
        },
        {
          "properties": {
            "name": { "type": "string" },
            "purpose": { "type": "string" },
            "rule": {
              "type": "string",
              "enum": ["all", "pick"]
            },
            "count": { "type": "integer", "minimum": 1 },
            "min": { "type": "integer", "minimum": 0 },
            "max": { "type": "integer", "minimum": 0 },
            "from_nested": {
              "type": "array",
              "minItems": 1,
              "items": {
                "$ref": "#/definitions/submission_requirement"
              }
            }
          },
          "required": ["rule", "from_nested"],
          "additionalProperties": false
        }
      ]
    }
  },
  "$ref": "#/definitions/submission_requirement"
}

Using the workaround from @mmdk95 for now

ManAnRuck commented 8 months ago

i have the same problem with the openapi schema (3.1) https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.1/schema.json

mathematikoi commented 6 months ago

i have the same problem with the openapi schema (3.1) https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.1/schema.json

same

Bluscream commented 2 months ago

I have the same issue with ALL schemas from https://app.quicktype.io/schema