JSONSchemaType doesn't infer nullable types
What version of Ajv are you using? Does the issue happen if you use the latest version? 8.11.2 This issue is not present in 8.11.0
Your typescript code
import { JSONSchemaType } from "ajv";
const schema: JSONSchemaType<{ foo: string | null }> = {
type: "object",
properties: {
foo: { type: "string", nullable: true },
},
required: ["foo"],
};
void schema;
Typescript compiler error messages
Type '{ type: "object"; properties: { foo: { type: "string"; nullable: true; }; }; required: "foo"[]; }' is not assignable to type 'UncheckedJSONSchemaType<{ foo: string | null; }, false>'.
The types of 'properties.foo' are incompatible between these types.
Type '{ type: "string"; nullable: true; }' is not assignable to type '{ $ref: string; } | ({ anyOf: readonly UncheckedJSONSchemaType<string | null, false>[]; } & { [keyword: string]: any; $id?: string | undefined; $ref?: string | undefined; $defs?: Record<...> | undefined; definitions?: Record<...> | undefined; } & { ...; }) | ({ ...; } & ... 1 more ... & { ...; }) | ({ ...; } & ... 2...'.
Types of property 'nullable' are incompatible.
Type 'true' is not assignable to type 'false'.ts(2322)
Describe the change that should be made to address the issue? There should be no TypeScript errors. As mentioned, this does not give any compiler errors in version 8.11.0
Are you going to resolve the issue? I'm not familiar with the internals of ajv
I get the exact same problem right on my project but I think the new behaviour is closer to JsonSchema specification
And BTW, if I try to put ['string', null] as a type in my JSONSchemaType I get this error:
Type '{ type: ("number" | null)[]; }' is not assignable to type '{ type: "number" | "integer"; }'.
I found the same problem, I downgraded to version 8.11.0 meanwhile
Seem's that this is a bug introduced with the commit https://github.com/ajv-validator/ajv/commit/00b3939ba545e87f585b5ee5e93d26f025454fc6
IMO it's against the JSON Schema specification.
JSON Schema imposes no restrictions on type: JSON Schema can describe any JSON value, including, for example, null http://json-schema.org/draft/2020-12/json-schema-core.html
And from the link @juliensnz posted It’s important to remember that in JSON, null isn’t equivalent to something being absent.
With this change, it became impossible to define a required field that has a null value.
{
type: "object",
properties: {
foo: { type: "null" },
},
required: ["foo"],
}
While it should be perfectly fine.
By the way, from this commit message "nullable was enforced for optional parameters" - I think this is also wrong. I might want to have an optional, string field that can't be null if it's present. In this case only a string should be accepted or undefined value. null shouldn't be accepted. Nullable and non-required are different things similarly as null and "absent" are different concepts in JSON.
Same problem with {type: 'integer', nullable: true}
Any progress? Will this be tackled?
Same issue for me:
type User = {
firstName: string | null
}
const userSchema: JSONSchemaType<User> = {
type: "object",
properties: {
firstName: {
type: "string",
nullable: true
}
},
required: [
"firstName"
]
}
The types of 'properties.firstName' are incompatible between these types.
...
Types of property 'nullable' are incompatible.
Type 'true' is not assignable to type 'false'.
I have to change my type from string | null to string | undefined, which makes no sense. In both TS and JSON there's a difference between "set x to nothing" and "I didn't mention x" ;-)
As a workaround I found that this works:
type MyType = {
field: string | null;
};
const schema: JSONSchemaType<MyType> = {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
required: [
'field',
],
properties: {
'field': {
- type: 'string',
- pattern: '...',
- nullable: true,
+ oneOf: [
+ {
+ type: 'string',
+ pattern: '...',
+ },
+ {
+ type: 'null',
+ nullable: true,
+ },
+ ],
},
},
};
(though I'm not happy about it)
another solution (not prettier) could be to do:
type: ['string', 'null'] as unknown as 'string',
For reference, document about migrating from OpenAPI 3.0 to 3.1 describes it as
// OpenAPI v3.0
{ type: 'string', nullable: true }
// OpenAPI v3.1
{ type: ['string', 'null'] }
Hi, I am encountering the same issue on the latest version of AJV (8.12.0). Not only the Types were broken in this minor version, but they are now recommending WRONG schema structure, since it reports errors on all the CORRECT usage.
Here is the setup:
const Ajv = require('ajv')
const ajv = new Ajv({})
const schema = /** @type {import('ajv').JSONSchemaType<{ test: string | null }>} */ ({
type: 'object',
properties: {
// test: { anyOf: [{ type: 'string' }, { const: 'null' }] }, // CORRECT, but AJV reports TS error
// test: { type: 'string', nullable: true }, // CORRECT, but AJV reports TS error
// test: { type: ['string', 'null'] }, // CORRECT, but AJV reports TS error
test: { type: 'string', const: null }, // WRONG, but AJV types suggest it
},
required: ['test'],
})
const validate = ajv.compile(schema)
const examples = [{
test: null
}, {
test: 'some text'
}]
for (const example of examples) {
console.log()
console.log('Schema: %o', example)
const valid = validate(example)
if (!valid) {
console.log('Error:', validate.errors)
} else {
console.log('Valid')
}
}
console.log()
console.log('All done')
Here is the type generated for import('ajv').JSONSchemaType<{ test: string | null }>.
type ComputedSchemaType =
| { $ref: string }
| ({ anyOf: readonly UncheckedJSONSchemaType<string | null, false>[] } & {
[keyword: string]: any
$id?: string | undefined
$ref?: string | undefined
$defs?: Record<string, UncheckedJSONSchemaType<Known, true>> | undefined
definitions?: Record<string, UncheckedJSONSchemaType<Known, true>> | undefined
} & {
nullable?: false | undefined
const?: string | null | undefined
enum?: readonly (string | null)[] | undefined
default?: string | null | undefined
})
| ({ oneOf: readonly UncheckedJSONSchemaType<string | null, false>[] } & {
[keyword: string]: any
$id?: string | undefined
$ref?: string | undefined
$defs?: Record<string, UncheckedJSONSchemaType<Known, true>> | undefined
definitions?: Record<string, UncheckedJSONSchemaType<Known, true>> | undefined
} & {
nullable?: false | undefined
const?: string | null | undefined
enum?: readonly (string | null)[] | undefined
default?: string | null | undefined
})
| ({ type: readonly 'string'[] } & StringKeywords & {
[keyword: string]: any
$id?: string | undefined
$ref?: string | undefined
$defs?: Record<string, UncheckedJSONSchemaType<Known, true>> | undefined
definitions?: Record<string, UncheckedJSONSchemaType<Known, true>> | undefined
} & {
nullable?: false | undefined // WRONG - should allow nullable
const?: string | null | undefined
enum?: readonly (string | null)[] | undefined
default?: string | null | undefined
})
| ({ type: 'string' } & StringKeywords & {
allOf?: readonly UncheckedPartialSchema<string | null>[] | undefined
anyOf?: readonly UncheckedPartialSchema<string | null>[] | undefined
oneOf?: readonly UncheckedPartialSchema<string | null>[] | undefined
if?: UncheckedPartialSchema<string | null> | undefined
then?: UncheckedPartialSchema<string | null> | undefined
else?: UncheckedPartialSchema<string | null> | undefined
not?: UncheckedPartialSchema<string | null> | undefined
} & {
[keyword: string]: any
$id?: string | undefined
$ref?: string | undefined
$defs?: Record<string, UncheckedJSONSchemaType<Known, true>> | undefined
definitions?: Record<string, UncheckedJSONSchemaType<Known, true>> | undefined
} & {
nullable?: false | undefined // WRONG - should allow nullable
const?: string | null | undefined
enum?: readonly (string | null)[] | undefined
default?: string | null | undefined
})
As additional information, the typechecking broke in 8.11.1 patch.
Also ran into this issue. It appears the problem is with the definition of Nullable<T>:
type Nullable<T> = undefined extends T
? {
nullable: true
const?: null // any other value here would make `null` fail
enum?: (T | null)[] // `null` must be explicitely included in "enum"
default?: T | null
}
: {
const?: T
enum?: T[]
default?: T
}
This type definition matches optional properties rather than nullable ones, and therefore declaring a nullable property as per the definition in the Ajv JSON Schema documentation will cause a Typescript compilation error.
This was apparently fixed by #1719, but the change was deemed breaking and pushed to the v9 branch, which has been gathering dust since 2021.
any update on this?
This probably doesn't help but it appears if you make the field optional (e.g. add ?) then the schema works.
e.g.
import { JSONSchemaType } from "ajv";
const schema: JSONSchemaType<{ foo?: string | null }> = {
type: "object",
properties: {
foo: { type: "string", nullable: true },
},
required: ["foo"],
};
void schema;
In my case ajv was coercing null values to 0 since the nullable field was an integer -- by adding ? I was able to add nullable: true and ajv also stopped coercing.
Hello everyone, who is encountered this issue! What helped me here to get rid of this issue at least temporary:
- Use normal scheme links, no need for ? or something else, like it was done in the initial issue message
import { JSONSchemaType } from "ajv"; const schema: JSONSchemaType<{ foo: string | null }> = { type: "object", properties: { foo: { type: "string", nullable: true }, }, required: ["foo"], }; void schema;
- Use
ajvversion8.11.0 - Use
typescriptversion5.0.4: for exampletypescript 5.2.2proceeded reproduce this issue even with previously mentioned item.
Overall, looks like issue connected not only with ajv, but with typescript dependency changes too, unfortunately, there is not enough time for taking deeper look into possible reasons. Hope, that it will help someone to resolve it locally in your projects and maybe found the root cause and fix it in the next update.
I will top this all with this workaround:
{ type: 'string', nullable: true as false }
Just see it as a Zen Koan
And if you embed this into another schema within $defs you must not use 'nullable' (8.17.1)
e.g.
$defs : {
xyz: {
properties: {
value : { oneOf: [schema] } }
now I have to duplicate the code
at least the behavior must be consistent to follow the DRY principle.
Ran into this issue as well using ajv v8.12.0 and Typescript v5.3.3
I encountered this after following OpenAI's suggestions to use null unions https://platform.openai.com/docs/guides/structured-outputs/all-fields-must-be-required. I'm not sure how to specify the field in the required array and also have it be nullable (either with a null union or nullable: true) in a way that makes TypeScript happy.
Same issue here, specifically when looking at using AJV to define JSON schemas for OpenAI structured outputs. I downgraded to 8.11.0 and the type error went away.
Similar issue. Can't figure out what's the issue:
export type LabelValueType = {
value: string;
label: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[rest: string]: any;
};
type FilterFormData = {
tripCodes?: LabelValueType | LabelValueType[] | null;
};
// Schema for validation
const filterSchema: JSONSchemaType<FilterFormData> = {
type: 'object',
properties: {
tripCodes: {
type: 'array',
items: {
type: 'object',
properties: {
value: { type: 'string' },
label: { type: 'string' },
},
required: ['value', 'label'],
},
nullable: true,
},
},
required: ['tripCodes'],
};
With Nullable:
export type LabelValueType = {
value: string; // <== also tried "string | null" here but same error
label: string; // <== also tried "string | null" here but same error
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[rest: string]: any;
};
type FilterFormData = {
tripCodes?: LabelValueType | LabelValueType[] | null;
};
// Schema for validation
const filterSchema: JSONSchemaType<FilterFormData> = {
type: 'object',
properties: {
tripCodes: {
type: 'array',
items: {
type: 'object',
properties: {
value: { type: 'string', nullable: true },
label: { type: 'string', nullable: true },
},
additionalProperties: true,
required: ['value', 'label'],
},
nullable: true,
},
},
required: ['tripCodes'],
};
https://ibb.co/4gnB2kF7