json-schema-to-typescript icon indicating copy to clipboard operation
json-schema-to-typescript copied to clipboard

Improve output for top level $ref

Open dherges opened this issue 7 years ago • 17 comments

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[];
}

dherges avatar Nov 16 '17 12:11 dherges

Thanks for the reports @dherges.

@BigstickCarpet Is Example A kosher?

bcherny avatar Nov 16 '17 16:11 bcherny

@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.

bcherny avatar Nov 16 '17 17:11 bcherny

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 avatar Nov 16 '17 20:11 dherges

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;
}

dherges avatar Nov 16 '17 20:11 dherges

@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.

JamesMessinger avatar Nov 17 '17 17:11 JamesMessinger

@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 avatar Nov 17 '17 18:11 bcherny

@dherges Can you chime in?

bcherny avatar Nov 28 '17 20:11 bcherny

Yes, exactly!

dherges avatar Nov 28 '17 21:11 dherges

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

zoonman avatar Mar 01 '21 19:03 zoonman

Any update on this?

k2on avatar Mar 27 '21 18:03 k2on

I am stil getting this error any workarounds?

ShivamJoker avatar Mar 15 '22 10:03 ShivamJoker

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
$ref: "#/definitions/child"
definitions:
  child:
     description: Hello World
{
   "$ref": "#/definitions/child",
   "definitions": {
       "child": {
          "description": "Hello World",
       }  
   }
}

You can do this, and your types should generate

YAML JSON
allOf:
- $ref: "#/definitions/child"
definitions:
  child:
    description: Hello World
{
   "allOf": [
      { "$ref": "#/definitions/child" }
   ],
   "definitions": {
       "child": {
          "description": "Hello World",
       }  
   }
}

mmdk95 avatar Sep 12 '22 16:09 mmdk95

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": {...}
    }
  }
}

zanona avatar Feb 11 '23 08:02 zanona

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

antonio-ivanovski avatar Oct 19 '23 12:10 antonio-ivanovski

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

ManAnRuck avatar Feb 12 '24 19:02 ManAnRuck

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

mathematikoi avatar Apr 19 '24 03:04 mathematikoi