bcherny/json-schema-to-typescript

discrepancy between an internal and external references resolution (`$ref`)

Open

#143 opened on Feb 1, 2018

View on GitHub
 (7 comments) (5 reactions) (0 assignees)TypeScript (449 forks)github user discovery
bughelp wanted

Repository metrics

Stars
 (3,302 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

Hi @bcherny,

Once again, thanks for your great work!

I'm having a strange situation, might be considered as a bug, or a feature... depends on your interpretation. For me it's kinda inconsistent.

I have a scenario.schema.json file where I define an entity, it has internal sub-definitions (used for money, enums, etc.). It looks like the following:

{
  "title": "Scenario",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "faker": "random.uuid"
    },
    "name": {
      "type": "string",
      "faker": {
        "fake": "{{company.bsNoun}} improvement"
      }
    },
    "description": {
      "type": "string",
      "faker": "lorem.sentences"
    },
    "sharing": {
      "$ref": "#/definitions/ScenarioSharing"
    },
    "currentVersion": {
      "type": "string",
      "pattern": "v0\\.\\d"
    },
    "lastRun": {
      "type": "string",
      "faker": "date.past"
    },
    "versions": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/VersionDefinition"
      }
    },
    "summary": {
      "$ref": "#/definitions/ScenarioSummary"
    }
  },
  "additionalProperties": false,
  "required": [
    "id",
    "name",
    "description",
    "sharing",
    "currentVersion",
    "lastRun",
    "versions",
    "summary"
  ],
  "definitions": {
    "ScenarioSharing": {
      "enum": [
        "PUBLIC",
        "PRIVATE"
      ]
    },
    "Currency": {
      "enum": [
        "USD",
        "EUR",
        "CHF",
        "GBP"
      ]
    },
    "VersionDefinition": {
      "type": "object",
      "properties": {
        "version": {
          "type": "string",
          "pattern": "v0\\.\\d"
        },
        "date": {
          "type": "string",
          "faker": {
            "date.between": ["2018-01-01", "2018-12-31"]
          }
        },
        "editor": {
          "type": "string",
          "faker": "name.findName"
        },
        "notes": {
          "type": "string",
          "faker": "lorem.sentence"
        }
      },
      "additionalProperties": false,
      "required": [
        "version",
        "date",
        "editor",
        "notes"
      ]
    },
    "ScenarioSummary": {
      "type": "object",
      "properties": {
        "startDate": {
          "type": "string",
          "faker": {
            "date.between": ["2019-01-01", "2019-12-31"]
          }
        },
        "endDate": {
          "type": "string",
          "faker": {
            "date.between": ["2020-01-01", "2020-12-31"]
          }
        },
        "projectCost": {
          "$ref": "#/definitions/Money"
        },
        "BAUCost": {
          "$ref": "#/definitions/Money"
        }
      },
      "additionalProperties": false,
      "required": [
        "startDate",
        "endDate",
        "projectCost",
        "BAUCost"
      ]
    },
    "Money": {
      "type": "object",
      "properties": {
        "amount": {
          "type": "number",
          "faker": {
            "random.number": {
              "min": 100000,
              "max": 10000000,
              "precision": 10000
            }
          }
        },
        "currency": {
          "$ref": "#/definitions/Currency"
        }
      },
      "additionalProperties": false,
      "required": [
        "amount",
        "currency"
      ]
    }
  }
}

I have another file which is just a collection of these entities:

{
  "title": "Scenarios",
  "type": "array",
  "items": {
    "$ref": "api/modelling/scenario.schema.json"
  }
}

. I use both files in a RAML contract (generating html docs with raml2html and .d.ts files with your lib here). They need separate schema files, since GET /scenarios is different than GET /scenarios/{id}.

For a single scenario schema I get following:

// Auto-generated with `raml-to-typescript`/`json-schema-to-typescript`

export type ScenarioSharing = "PUBLIC" | "PRIVATE";
export type Currency = "USD" | "EUR" | "CHF" | "GBP";

export interface Scenario {
  id: string;
  name: string;
  description: string;
  sharing: ScenarioSharing;
  currentVersion: string;
  lastRun: string;
  versions: VersionDefinition[];
  summary: ScenarioSummary;
}
export interface VersionDefinition {
  version: string;
  date: string;
  editor: string;
  notes: string;
}
export interface ScenarioSummary {
  startDate: string;
  endDate: string;
  projectCost: Money;
  BAUCost: Money;
}
export interface Money {
  amount: number;
  currency: Currency;
}

whereas for array with external reference, I get:

// Auto-generated with `raml-to-typescript`/`json-schema-to-typescript`

export type Scenarios = Scenario[];

export interface Scenario {
  id: string;
  name: string;
  description: string;
  sharing: "PUBLIC" | "PRIVATE";
  currentVersion: string;
  lastRun: string;
  versions: {
    version: string;
    date: string;
    editor: string;
    notes: string;
  }[];
  summary: {
    startDate: string;
    endDate: string;
    projectCost: {
      amount: number;
      currency: "USD" | "EUR" | "CHF" | "GBP";
    };
    BAUCost: {
      amount: number;
      currency: "USD" | "EUR" | "CHF" | "GBP";
    };
  };
}

Semantically (from TS point of view), the structures are the same. However, internal references get normalized and "compiled into" the external file. I think it would be better if you normalized schemas after all $ref get resolved (no intermediate resolutions along the way).

What is your opinion on this?

Contributor guide