json-schema-to-typescript
json-schema-to-typescript copied to clipboard
allOf, oneOf dont correctly generate types
The following schema:
"Test": {
"type": "object",
"properties": {
"prop1": { "type": "string" },
"prop2": { "type": "string" },
"prop3": { "type": "string"},
"prop4": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
},
"prop5": { "type": "string" },
"prop6": { "type": "string" }
},
"allOf": [
{ "required": [ "prop1" ] },
{
"oneOf": [
{ "required": [ "prop2" ] },
{ "required": [ "prop3" ] },
{ "required": [ "prop4" ] },
{ "required": [ "prop5" ] },
{ "required": [ "prop6" ] }
]
}
],
"additionalProperties": false
}
Generates type:
type Test = {
[k: string]: unknown;
} & (
| {
[k: string]: unknown;
}
| {
[k: string]: unknown;
}
| {
[k: string]: unknown;
}
| {
[k: string]: unknown;
}
| {
[k: string]: unknown;
}
);
Expected type:
type Test = {
prop1: string;
} & (
{ prop2: string } |
{ prop3: string } |
{ prop4: string[] } |
{ prop5: string } |
{ prop6: string }
);
Similar to https://github.com/bcherny/json-schema-to-typescript/issues/378 ?
Used version 10.1.4
@italicbold the mentioned issue is related to the use of allOf, anyOf with properties, and in that case there was an error in the provided example. Here the problem is related to the processing of requested
property when inside oneOf, anyOf. I think it is different.
I'm seeing the same problem. Usage of anyOf
causes schema to devolve to [k: string]: unknown;
I found that nesting oneOf
/anyOf
/allOf
causes the issue:
Minimum example schema:
{
"oneOf": [ // or "allOf" or "anyOf"
{
"properties": {
"speed": { "type": "string" }
},
"oneOf": [ // or "allOf" or "anyOf"
{ "required": ["speed"] }
]
}
]
}
Expected vs. actual output
Expected output:
export type Demo = {
speed?: string;
[k: string]: unknown;
};
Actual output:
export type Demo = {
[k: string]: unknown;
};
Workaround that yields the expected output
{
"oneOf": [ // or "allOf" or "anyOf"
{
"properties": {
"speed": { "type": "string" }
},
+ "if": true,
+ "then": {
"oneOf": [ // or "allOf" or "anyOf"
{ "required": ["speed"] }
]
+ }
}
]
}
Example schema with a bit more context
{
"oneOf": [
{
"properties": {
"type": { "const": "NoFunction" }
},
"required": ["type"],
"additionalProperties": false
},
{
"properties": {
"type": { "const": "ShutterStrobe" },
"speed": { "type": "string" },
"speedStart": { "type": "string" },
"speedEnd": { "type": "string" }
},
"required": ["type"],
"oneOf": [
{ "required": ["speed"] },
{ "required": ["speedStart"] }
],
"dependencies": {
"speedStart": ["speedEnd"],
"speedEnd": ["speedStart"]
},
"additionalProperties": false
}
]
}
When removing the inner oneOf
/anyOf
/allOf
, or when not being nested in an outer oneOf
/anyOf
/allOf
, the output is as expected.
AFAICT, it isn't valid JSON schema to use { required: ["property"] }
as an allOf
item. JSON schema's docs say:
allOf can not be used to “extend” a schema to add more details to it in the sense of object-oriented inheritance. Instances must independently be valid against “all of” the schemas in the allOf. See the section on Subschema Independence for more information.
(Source)
So there doesn't seem to be a problem with json-schema-to-typescript
. As for how to generate the TypeScript interface you want, I think the following code achieves the behavior you're looking for. It's more verbose than you'd like, but IMO it's also easier to understand what's going on in the schema.
JSON Schema
View
{
"title": "Test",
"type": "object",
"allOf": [
{
"type": "object",
"properties": {
"prop1": { "type": "string" },
"prop2": { "type": "string" },
"prop3": { "type": "string" },
"prop4": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
},
"prop5": { "type": "string" },
"prop6": { "type": "string" }
},
"additionalProperties": false
},
{
"type": "object",
"properties": { "prop1": { "type": "string" } },
"required": ["prop1"],
"additionalProperties": false
},
{
"oneOf": [
{
"type": "object",
"properties": { "prop2": { "type": "string" } },
"required": ["prop2"],
"additionalProperties": false
},
{
"type": "object",
"properties": { "prop3": { "type": "string" } },
"required": ["prop3"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"prop4": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
}
},
"required": ["prop4"],
"additionalProperties": false
},
{
"type": "object",
"properties": { "prop5": { "type": "string" } },
"required": ["prop5"],
"additionalProperties": false
},
{
"type": "object",
"properties": { "prop6": { "type": "string" } },
"required": ["prop6"],
"additionalProperties": false
}
]
}
]
}
Output
export type Test = {
prop1?: string;
prop2?: string;
prop3?: string;
prop4?: [string, ...string[]];
prop5?: string;
prop6?: string;
} & {
prop1: string;
} & (
| {
prop2: string;
}
| {
prop3: string;
}
| {
prop4: [string, ...string[]];
}
| {
prop5: string;
}
| {
prop6: string;
}
);
Reading this and this, I would argue, that { "required": ["foo"] }
is a perfectly valid schema, that means "if the value is an object, it must have a property foo
". In terms of TypeScript that would be Not<object> | { foo: unknown }
except there is no Not
and I know of no way to express such a thing in TS.
Any news on this one? Is someone working on it?