ts-json-schema-generator
ts-json-schema-generator copied to clipboard
Incorrect schema generation when using recursive mapped types
Hi there! I have run into a few bugs while trying to generate schemas using recursive mapped types.
- Unions are incorrectly merged into a single definition
- Comments are lost/replaced with comment from the mapped type
- Property modifiers are sometimes lost (like readonly, optional) however I can't yet properly reproduce
Given the following set of types:
type A = {
/**
* Some comment on property a
*/
a: string
}
type B = {
/**
* Some comment on property b
*/
b?: string
y: string
}
type ComposedWithIntersection = (A | B) & {
model: string
}
type ComposedSingle = A | B
export type Parent = {
prop: string
composed_single: ComposedSingle[]
composed_with_intersection: ComposedWithIntersection
}
type Primitive = string | number | boolean | null | undefined
type ComplexProperty = Date
/**
* Comment on mapped type. Should not appear on usages
*/
type SerializedDeep<T> = T extends Primitive
? T
: T extends ComplexProperty
? string
: {
[P in keyof T]: SerializedDeep<T[P]>
}
export type SerializedParent = SerializedDeep<Parent>
This will produce the following incorrect schema:
Generated Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Parent": {
"additionalProperties": false,
"properties": {
"composed_single": {
"items": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"a": {
"description": "Some comment on property a",
"type": "string"
}
},
"required": ["a"],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"b": {
"description": "Some comment on property b",
"type": "string"
},
"y": {
"type": "string"
}
},
"required": ["y"],
"type": "object"
}
]
},
"type": "array"
},
"composed_with_intersection": {
"anyOf": [
{
"additionalProperties": false,
"properties": {
"a": {
"description": "Some comment on property a",
"type": "string"
},
"model": {
"type": "string"
}
},
"required": ["a", "model"],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"b": {
"description": "Some comment on property b",
"type": "string"
},
"model": {
"type": "string"
},
"y": {
"type": "string"
}
},
"required": ["model", "y"],
"type": "object"
}
]
},
"prop": {
"type": "string"
}
},
"required": ["prop", "composed_single", "composed_with_intersection"],
"type": "object"
},
"SerializedParent": {
"additionalProperties": false,
"description": "Comment on mapped type. Should not appear on usages",
"properties": {
"composed_single": {
"description": "Comment on mapped type. Should not appear on usages",
"items": {
"additionalProperties": false,
"description": "Comment on mapped type. Should not appear on usages",
"properties": {
"a": {
"description": "Comment on mapped type. Should not appear on usages",
"type": "string"
},
"b": {
"description": "Comment on mapped type. Should not appear on usages",
"type": "string"
},
"y": {
"description": "Comment on mapped type. Should not appear on usages",
"type": "string"
}
},
"required": ["a", "y"],
"type": "object"
},
"type": "array"
},
"composed_with_intersection": {
"additionalProperties": false,
"description": "Comment on mapped type. Should not appear on usages",
"properties": {
"a": {
"description": "Comment on mapped type. Should not appear on usages",
"type": "string"
},
"b": {
"description": "Comment on mapped type. Should not appear on usages",
"type": "string"
},
"model": {
"description": "Comment on mapped type. Should not appear on usages",
"type": "string"
},
"y": {
"description": "Comment on mapped type. Should not appear on usages",
"type": "string"
}
},
"required": ["a", "model", "y"],
"type": "object"
},
"prop": {
"description": "Comment on mapped type. Should not appear on usages",
"type": "string"
}
},
"required": ["prop", "composed_single", "composed_with_intersection"],
"type": "object"
}
}
}
Here you can compare the generation of Parent and SerializedParent. You will see that SerializedParent's composed_single and composed_with_intersection properties have merged their unions in an incorrect manner.
Additionally, the comments for property a and property b have been lost and all properties have inherited the comment from the mapped type.
I also want to report having run into an issue with optional properties having their optionality lost (they would become required in the schema) however I haven't been able to successfully reproduce - so I will update this issue once I can. I reworked the mapped type to fix this issue and cannot remember what it was originally.
I would love to help contribute to fixing this issue, but reading through the code it is way over my head :)
Similar issue: https://github.com/vega/ts-json-schema-generator/issues/126
From digging in the code a bit it seems the union issue is somewhere around here:
https://github.com/vega/ts-json-schema-generator/blob/next/src/NodeParser/MappedTypeNodeParser.ts#L38
As this function incorrectly returns the merged types. But I don't understand enough to see in what way this should change
Would it be possible for someone to take a look at this?