bcherny / json-schema-to-typescript

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

Incorrect Generation if oneOf is used mixed with properties #637

Open martineaus83 opened 1 month ago

martineaus83 commented 1 month ago

Incorrect Generation if oneOf is used mixed with properties. Exemple bellow :

{
    "$id": "http://dc.exail.com/schema-json/message_envelop",
    "title": "Generic Message Envelop",
    "description": "Generic message envelop",
    "type": "object",
    "properties": {
        "id": {
            "type": "integer",
            "description": "id of the message. It must be unique for a web socket connection. When a response is send, this id is set to id_previous"
        },
        "type": {
            "type": "string",
            "enum": ["request","event", "response","error-request"],
            "description": "type of the message. A request is followed by a response. An event doesn't expect a response."
        },
        "data": {
            "description": "Data owned by this message",
            "type": "object"
        }
    },
    "oneOf":   [
        {
            "properties": {
                "type": {
                    "type": "string",
                    "enum": ["request","event"],
                    "description": "type of the message. A request is followed by a response. An event doesn't expect a response."
                },
                "dataId": {
                    "type": "string",
                    "description": "identifier of the data property. With it, receiver knows how to interpret the data"
                }
            },
            "required": ["dataId"],
            "additionalProperties": false
        },
        {
            "properties": {
                "type": {
                    "type": "string",
                    "enum": ["response"],
                    "description": "type of the message. A request is followed by a response. An event doesn't expect a response."
                },
                "requestId": {
                    "type": "integer",
                    "description": "id of the previous Request message. Muste be set only for a response type."
                },
                "dataId": {
                    "type": "string",
                    "description": "identifier of the data property. With it, receiver knows how to interpret the data"
                },
                "data": {
                    "description": "Data owned by this message",
                    "type": "object"
                }
            },
            "required": ["requestId","dataId","data"],
            "additionalProperties": false
        },
        {
            "properties": {
                "id": {
                    "type": "integer",
                    "description": "id of the message. It must be unique for a web socket connection. When a response is send, this id is set to id_previous"
                },
                "requestId": {
                    "type": "integer",
                    "description": "id of the previous request message."
                },
                "type": {
                    "type": "string",
                    "enum": ["error-request"],
                    "description": "If the request isn't valid an error-request must be sent to inform requester"
                },
                "data": {
                    "description": "Data owned by this message",
                    "type": "string"
                }
            },
            "required": ["id","requestId","type","data"],
            "additionalProperties": false
        }
    ],
    "required": ["id","type"],
    "additionalProperties": false
}

The generated Code is :

/* eslint-disable */
/**
 * This file was automatically generated by json-schema-to-typescript.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
 * and run json-schema-to-typescript to regenerate this file.
 */

/**
 * Generic message envelop
 */
export type GenericMessageEnvelop = GenericMessageEnvelop1 & GenericMessageEnvelop2;
export type GenericMessageEnvelop2 =
  | {
      /**
       * type of the message. A request is followed by a response. An event doesn't expect a response.
       */
      type?: "request" | "event";
      /**
       * identifier of the data property. With it, receiver knows how to interpret the data
       */
      dataId: string;
    }
  | {
      /**
       * type of the message. A request is followed by a response. An event doesn't expect a response.
       */
      type?: "response";
      /**
       * id of the previous Request message. Muste be set only for a response type.
       */
      requestId: number;
      /**
       * identifier of the data property. With it, receiver knows how to interpret the data
       */
      dataId: string;
      /**
       * Data owned by this message
       */
      data: {
        [k: string]: unknown;
      };
    }
  | {
      /**
       * id of the message. It must be unique for a web socket connection. When a response is send, this id is set to id_previous
       */
      id: number;
      /**
       * id of the previous request message.
       */
      requestId: number;
      /**
       * If the request isn't valid an error-request must be sent to inform requester
       */
      type: "error-request";
      /**
       * Data owned by this message
       */
      data: string;
    };

GenericMessageEnvelop1 isn't generated.

Proposition of change in generator.ts in declareNamedTypes function. In "INTERFACE" case, add the "UNION" case code. So, the properties will be generated :

    case 'INTERFACE':
      const superclass = getSuperTypesAndParams(ast)
        .map(
          ast =>
            (ast.standaloneName === rootASTName || options.declareExternallyReferenced) &&
            declareNamedTypes(ast, options, rootASTName, processed),
        )
        .filter(Boolean)
        .join('\n');
      const code = [
        hasStandaloneName(ast) ? generateStandaloneType(ast, options) : undefined,
        ast.params
          .map(ast => declareNamedTypes(ast, options, rootASTName, processed))
          .filter(Boolean)
          .join('\n'),
        'spreadParam' in ast && ast.spreadParam
          ? declareNamedTypes(ast.spreadParam, options, rootASTName, processed)
          : undefined,
        ]
        .filter(Boolean)
        .join('\n');
      return superclass+"\n"+code;