Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for nullable primitives #411

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,30 @@ rules.set('Transform const to singleton enum', schema => {
}
})

rules.set('Transform nullable -> anyOf', schema => {
if (schema.nullable === true) {
delete schema.nullable

const copiedSchema = {...schema}

// This stuff should not be in `anyOf`.
delete copiedSchema.id
delete copiedSchema.nullable

// This stuff will be in `anyOf` instead.
delete schema.format
delete schema.type
Comment on lines +155 to +160
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove all of these deletes? Unclear why you need them (156 is already included in 150; 155+159+160 I'm not sure what they're here for).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcherny

I think we need all of these deletes. After normalizing, we no longer want nullable, format, or type to appear in the root of the schema. That's what the anyOf is replacing.

For example, the following schema:

{
  "id": "foo",
  "type": "integer",
  "format": "int32",
  "nullable": true
}

Should be normalized to this:

 {
  "id": "foo",
  "anyOf": [
    {
      "type": "integer",
      "format": "int32"
    },
    {
      "type": "null"
    }
  ]
}

Notice how nullable is gone, and type and format have moved into the anyOf


schema.anyOf = [
copiedSchema,
// @ts-expect-error
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you rm this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get a TS error without it:

Type '{ type: "null"; }' is missing the following properties from type 'LinkedJSONSchema': additionalProperties, [Parent]ts(2739)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! Can you find a way to fix the error? eg. to add an empty additionalProperties, and to add a Parent field?

{
type: 'null'
}
]
}
})

export function normalize(rootSchema: LinkedJSONSchema, filename: string, options: Options): NormalizedJSONSchema {
rules.forEach(rule => traverse(rootSchema, schema => rule(schema, filename, options)))
return rootSchema as NormalizedJSONSchema
Expand Down
313 changes: 166 additions & 147 deletions test/__snapshots__/test/test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -5682,6 +5682,142 @@ Generated by [AVA](https://avajs.dev).
}␊
`

## realWorld.jsonschema.js

> Expected output to match snapshot for e2e test: realWorld.jsonschema.js

`/* tslint: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.␊
*/␊
export type CoreSchemaMetaSchema = CoreSchemaMetaSchema1 & CoreSchemaMetaSchema2;␊
export type NonNegativeInteger = number;␊
export type NonNegativeIntegerDefault0 = NonNegativeInteger;␊
export type CoreSchemaMetaSchema2 =␊
| {␊
$id?: string;␊
$schema?: string;␊
$ref?: string;␊
$comment?: string;␊
title?: string;␊
description?: string;␊
default?: true;␊
readOnly?: boolean;␊
writeOnly?: boolean;␊
examples?: true[];␊
multipleOf?: number;␊
maximum?: number;␊
exclusiveMaximum?: number;␊
minimum?: number;␊
exclusiveMinimum?: number;␊
maxLength?: NonNegativeInteger;␊
minLength?: NonNegativeIntegerDefault0;␊
pattern?: string;␊
additionalItems?: CoreSchemaMetaSchema2;␊
items?: CoreSchemaMetaSchema2 | SchemaArray;␊
maxItems?: NonNegativeInteger;␊
minItems?: NonNegativeIntegerDefault0;␊
uniqueItems?: boolean;␊
contains?: CoreSchemaMetaSchema2;␊
maxProperties?: NonNegativeInteger;␊
minProperties?: NonNegativeIntegerDefault0;␊
required?: StringArray;␊
additionalProperties?: CoreSchemaMetaSchema2;␊
definitions?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
properties?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
patternProperties?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
dependencies?: {␊
[k: string]: CoreSchemaMetaSchema2 | StringArray;␊
};␊
propertyNames?: CoreSchemaMetaSchema2;␊
const?: true;␊
enum?: [true, ...unknown[]];␊
type?: SimpleTypes | [SimpleTypes, ...SimpleTypes[]];␊
format?: string;␊
contentMediaType?: string;␊
contentEncoding?: string;␊
if?: CoreSchemaMetaSchema2;␊
then?: CoreSchemaMetaSchema2;␊
else?: CoreSchemaMetaSchema2;␊
allOf?: SchemaArray;␊
anyOf?: SchemaArray;␊
oneOf?: SchemaArray;␊
not?: CoreSchemaMetaSchema2;␊
[k: string]: unknown;␊
}␊
| boolean;␊
export type SchemaArray = [CoreSchemaMetaSchema2, ...CoreSchemaMetaSchema2[]];␊
export type StringArray = string[];␊
export type SimpleTypes = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string";␊
export interface CoreSchemaMetaSchema1 {␊
$id?: string;␊
$schema?: string;␊
$ref?: string;␊
$comment?: string;␊
title?: string;␊
description?: string;␊
default?: true;␊
readOnly?: boolean;␊
writeOnly?: boolean;␊
examples?: true[];␊
multipleOf?: number;␊
maximum?: number;␊
exclusiveMaximum?: number;␊
minimum?: number;␊
exclusiveMinimum?: number;␊
maxLength?: NonNegativeInteger;␊
minLength?: NonNegativeIntegerDefault0;␊
pattern?: string;␊
additionalItems?: CoreSchemaMetaSchema2;␊
items?: CoreSchemaMetaSchema2 | SchemaArray;␊
maxItems?: NonNegativeInteger;␊
minItems?: NonNegativeIntegerDefault0;␊
uniqueItems?: boolean;␊
contains?: CoreSchemaMetaSchema2;␊
maxProperties?: NonNegativeInteger;␊
minProperties?: NonNegativeIntegerDefault0;␊
required?: StringArray;␊
additionalProperties?: CoreSchemaMetaSchema2;␊
definitions?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
properties?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
patternProperties?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
dependencies?: {␊
[k: string]: CoreSchemaMetaSchema2 | StringArray;␊
};␊
propertyNames?: CoreSchemaMetaSchema2;␊
const?: true;␊
enum?: [true, ...unknown[]];␊
type?: SimpleTypes | [SimpleTypes, ...SimpleTypes[]];␊
format?: string;␊
contentMediaType?: string;␊
contentEncoding?: string;␊
if?: CoreSchemaMetaSchema2;␊
then?: CoreSchemaMetaSchema2;␊
else?: CoreSchemaMetaSchema2;␊
allOf?: SchemaArray;␊
anyOf?: SchemaArray;␊
oneOf?: SchemaArray;␊
not?: CoreSchemaMetaSchema2;␊
[k: string]: unknown;␊
}␊
`

## realWorld.openapi.js

> Expected output to match snapshot for e2e test: realWorld.openapi.js
Expand Down Expand Up @@ -8314,6 +8450,17 @@ Generated by [AVA](https://avajs.dev).
"required": []␊
}`

## Normalize empty const to singleton enum

> Snapshot 1

`{␊
"id": "foo",␊
"enum": [␊
""␊
]␊
}`

## Non object items.items

> Snapshot 1
Expand Down Expand Up @@ -8346,6 +8493,25 @@ Generated by [AVA](https://avajs.dev).
"additionalProperties": true␊
}`

## Normalize nullable to anyOf

> Snapshot 1

`{␊
"id": "foo",␊
"format": "int32",␊
"anyOf": [␊
{␊
"type": "integer",␊
"format": "int32"␊
},␊
{␊
"type": "null",␊
"additionalProperties": false␊
}␊
]␊
}`

## Remove `enum=[null]` if `type=['null']`

> Snapshot 1
Expand Down Expand Up @@ -8874,150 +9040,3 @@ Generated by [AVA](https://avajs.dev).
"additionalProperties": false,␊
"required": []␊
}`

## realWorld.jsonschema.js

> Expected output to match snapshot for e2e test: realWorld.jsonschema.js

`/* tslint: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.␊
*/␊
export type CoreSchemaMetaSchema = CoreSchemaMetaSchema1 & CoreSchemaMetaSchema2;␊
export type NonNegativeInteger = number;␊
export type NonNegativeIntegerDefault0 = NonNegativeInteger;␊
export type CoreSchemaMetaSchema2 =␊
| {␊
$id?: string;␊
$schema?: string;␊
$ref?: string;␊
$comment?: string;␊
title?: string;␊
description?: string;␊
default?: true;␊
readOnly?: boolean;␊
writeOnly?: boolean;␊
examples?: true[];␊
multipleOf?: number;␊
maximum?: number;␊
exclusiveMaximum?: number;␊
minimum?: number;␊
exclusiveMinimum?: number;␊
maxLength?: NonNegativeInteger;␊
minLength?: NonNegativeIntegerDefault0;␊
pattern?: string;␊
additionalItems?: CoreSchemaMetaSchema2;␊
items?: CoreSchemaMetaSchema2 | SchemaArray;␊
maxItems?: NonNegativeInteger;␊
minItems?: NonNegativeIntegerDefault0;␊
uniqueItems?: boolean;␊
contains?: CoreSchemaMetaSchema2;␊
maxProperties?: NonNegativeInteger;␊
minProperties?: NonNegativeIntegerDefault0;␊
required?: StringArray;␊
additionalProperties?: CoreSchemaMetaSchema2;␊
definitions?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
properties?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
patternProperties?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
dependencies?: {␊
[k: string]: CoreSchemaMetaSchema2 | StringArray;␊
};␊
propertyNames?: CoreSchemaMetaSchema2;␊
const?: true;␊
enum?: [true, ...unknown[]];␊
type?: SimpleTypes | [SimpleTypes, ...SimpleTypes[]];␊
format?: string;␊
contentMediaType?: string;␊
contentEncoding?: string;␊
if?: CoreSchemaMetaSchema2;␊
then?: CoreSchemaMetaSchema2;␊
else?: CoreSchemaMetaSchema2;␊
allOf?: SchemaArray;␊
anyOf?: SchemaArray;␊
oneOf?: SchemaArray;␊
not?: CoreSchemaMetaSchema2;␊
[k: string]: unknown;␊
}␊
| boolean;␊
export type SchemaArray = [CoreSchemaMetaSchema2, ...CoreSchemaMetaSchema2[]];␊
export type StringArray = string[];␊
export type SimpleTypes = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string";␊
export interface CoreSchemaMetaSchema1 {␊
$id?: string;␊
$schema?: string;␊
$ref?: string;␊
$comment?: string;␊
title?: string;␊
description?: string;␊
default?: true;␊
readOnly?: boolean;␊
writeOnly?: boolean;␊
examples?: true[];␊
multipleOf?: number;␊
maximum?: number;␊
exclusiveMaximum?: number;␊
minimum?: number;␊
exclusiveMinimum?: number;␊
maxLength?: NonNegativeInteger;␊
minLength?: NonNegativeIntegerDefault0;␊
pattern?: string;␊
additionalItems?: CoreSchemaMetaSchema2;␊
items?: CoreSchemaMetaSchema2 | SchemaArray;␊
maxItems?: NonNegativeInteger;␊
minItems?: NonNegativeIntegerDefault0;␊
uniqueItems?: boolean;␊
contains?: CoreSchemaMetaSchema2;␊
maxProperties?: NonNegativeInteger;␊
minProperties?: NonNegativeIntegerDefault0;␊
required?: StringArray;␊
additionalProperties?: CoreSchemaMetaSchema2;␊
definitions?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
properties?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
patternProperties?: {␊
[k: string]: CoreSchemaMetaSchema2;␊
};␊
dependencies?: {␊
[k: string]: CoreSchemaMetaSchema2 | StringArray;␊
};␊
propertyNames?: CoreSchemaMetaSchema2;␊
const?: true;␊
enum?: [true, ...unknown[]];␊
type?: SimpleTypes | [SimpleTypes, ...SimpleTypes[]];␊
format?: string;␊
contentMediaType?: string;␊
contentEncoding?: string;␊
if?: CoreSchemaMetaSchema2;␊
then?: CoreSchemaMetaSchema2;␊
else?: CoreSchemaMetaSchema2;␊
allOf?: SchemaArray;␊
anyOf?: SchemaArray;␊
oneOf?: SchemaArray;␊
not?: CoreSchemaMetaSchema2;␊
[k: string]: unknown;␊
}␊
`

## Normalize empty const to singleton enum

> Snapshot 1

`{␊
"id": "foo",␊
"enum": [␊
""␊
]␊
}`
Binary file modified test/__snapshots__/test/test.ts.snap
Binary file not shown.
21 changes: 21 additions & 0 deletions test/normalizer/nullableToAnyOf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "Normalize nullable to anyOf",
"in": {
"id": "foo",
"type": "integer",
"format": "int32",
"nullable": true
},
"out": {
"id": "foo",
"anyOf": [
{
"type": "integer",
"format": "int32"
},
{
"type": "null"
}
]
}
}