-
-
Notifications
You must be signed in to change notification settings - Fork 878
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
JSONSchemaType requires "required" and "additionalProperties", unless it is oneOf schema #1521
Comments
I will check it - what typescript version do you use? Could you try upgrading to 4.2.3? |
I'm running into the same error, using typescript 4.2.3 and ajv 8.0.1 (same error in 8.0.0 as well) |
I upgraded to 4.2.3 and still have the error. |
I understand the problem - this is not a bug, but with the addition of the union support error reporting became a bit more confusing - maybe something can be done about it (cc @erikbrinkman). The minimal correct schema for this type is (and it compiles correctly):
The reason for that is that the properties in the type are required, but JSON Schema without Please note that this schema still allows additional properties. An equivalent JSON Type Definition schema is more concise (the type is implied, properties are required and additional properties are not allowed by default):
|
The reason error message mentions oneOf - typescript probably chooses the latest branch in the union - maybe swapping the branches will fix it... |
Thanks for the quick response! Highly appreciated. :) |
Likewise, thank you for explaining this! |
I am sorry, but I have to come back to this. I now get another type error using JTDSchemaType. The example from the docs import { JTDSchemaType } from 'ajv/dist/jtd';
interface MyData {
foo: number;
bar?: string;
}
const schema: JTDSchemaType<MyData> = {
properties: {
foo: { type: 'int32' }, // error here
},
optionalProperties: {
bar: { type: 'string' }, // and here
},
}; fails with |
Ive looked into the code and it seems like the problem comes from the second generic which default to Record<string, never>. |
This example compiles for me, possibly you need TS 4.2.3. See these tests: https://github.com/ajv-validator/ajv/blob/master/spec/types/jtd-schema.spec.ts#L184
This is a non-trivial trade-off - I've tried to summarise it here: https://ajv.js.org/guide/schema-language.html#comparison |
@epoberezkin I have the same hunch you have about branches and will look into fixing it, because that's definitely not an ideal error message. |
typescript seems to report errors with the last member of a valid union first, so this rearranges the type so the simple union types come first, and the type that represents the meaty types comes last. This should help make errors like the one in ajv-validator#1521 more clear.
released in 8.0.3 |
With version 8.0.3 and TypeScript 4.2.3, I get a wired error Here an exemple to reproduce the error : https://replit.com/@BenjaminDaniel1/AJV-issue-1521 Does any one has any idea about that ? |
@BenjD90 thanks for figuring out it is because of strict mode is set to false! I can confirm that this is causing the issue above! |
if this is strictNullCheck - then yes, it’s required to be true for this type to work correctly. It was in the docs previously but looks like with the site migration it was lost - needs to be added. Btw, using this type is completely optional - it’s only needed for your validation function to be type guard, but you can also pass type parameter directly to ajv.compile - needs to be better documented. |
Obviously, without using JSONSchemaType there is no type level check for consistency of data type and schema - but if you need strictNullChecks to be false, it is not possible to achieve |
It's the |
@crobinson42 what is the actual error your getting? from the paste |
Hi, I am also getting a similar error message. I couldn't find out the solution.
Here is my environment: ❯ jq .version node_modules/typescript/package.json
"4.2.4"
❯ jq .version node_modules/ts-node/package.json
"9.1.1"
The output I get:
I couldn't figure out the root cause of this, I hope this helps. |
@fatihky this seems to be a bug in |
@erikbrinkman thank you, and I'm sorry if my comment is out of the scope of this issue. |
@fatihky I originally thought this was related to a recent diff I put up, but actually it's the same as the original post. You don't have @epoberezkin This seems to be a recurring problem, people forgetting to or not realizing the problem of not enabling strictNullChecks. It seems we can use
Do you have a preference or another idea to address this? |
indeed I like option 1 with better error reporting. I think we might be digging ourselves a hole trying to support both modes. I’m also not sure if RequiredMembers is the only problematic part without strictNullChecks - I think there will be some other issues with nullable/optional properties… Arguably, it’s beneficial to have strictNullChecks enabled anyway… In any case, better errors is a good short term option that would at least explain the problem on the spot (that is, if people read error messages - I often don’t…) |
@epoberezkin I'm sorry, but I'm ultimately not sure what you're suggesting? Just option 1? Unfortunately I think our only options are making it never, with some documentation that people hopefully look back to, or making it a string literal, and assuming that's "good enough" until typescript gives something better. |
Yes, option 1. |
typescript seems to report errors with the last member of a valid union first, so this rearranges the type so the simple union types come first, and the type that represents the meaty types comes last. This should help make errors like the one in ajv-validator#1521 more clear.
I think i triggered some bug that looks a lot like this now: If i use the following snippet:
Then I am getting the following error: Simplifying the interfaceFunnily however when i simplify my what if I include an empty
|
This isn't actually related because the past issue had to do with enabling strict null checks, whereas this seems to be an issue with accepting / requiring the "required" field, even though it can't be empty in draft 4. @epoberezkin I'm not immediately sure how JSONSchemaType is handled between versions, so I'm not sure what the appropriate fix is, since the requirement for "required" was to prevent other user errors. @HendrikPetertje in the short term, the schema is only used to help you, so if you really know that you don't want required you could do something like |
The properties in the TS interface are required, so the schema must have “required” with all properties to correspond to the interface. |
But in the example, stripped down, this schema type checks fine:
However, draft 4 says that "required" can't be empty. But given that it's a record, there are no required fields. Digging in a little further, it seems the problem is that here we check if there are any required members: Line 118 in 948ffbf
What's happening is that the inner Record isn't undefined, so JSONSchemaType thinks every string must have a defined record attached. Switching to This raises the question of JSONSchemaType should handle This question also raises another weakness of JSONSchemaType handing of records. JSONSchemaType should probably mandate additionalProperties for all records, and if the type isn't unknown, should probably mandate a schema for inner types, in the same way that JTD does. |
Meta: JSONSchemaType is awesome and I love it, thank you so much for this library and this class. My system's OpenAPI specification is my schema definition and type declaration, required, type checked, and automatically validated for every HTTP request and other input. This is accelerating a standard of secure development quite a lot. I landed here because I had a bug in my code resulting in I had an object with optional and nullable attributes ($type extends 'aws-lambda'.APIGatewayProxyStructuredResultV2) that I was narrowing. That object included 5 snazzy steps to non-existence (my simplified compiler error/fix sequence over too long a period of time):
It seems that a check for the contradiction could be useful for users interface Base {
attr1?: number | undefined
attr2?: {
[attr3: string]: boolean | number | string
} | undefined
attr3?: string | undefined
}
interface Mine extends Base {}
const schema1: JSONSchemaType<Mine> = {
type: 'object',
properties: {
attr1: {
type: 'number',
// nullable: true, // missing
},
attr2: {
type: 'object',
patternProperties: {
'^.+$': { type: 'string' },
},
propertyNames: {
pattern: '^[a-zA-Z0-9_-]+$',
},
},
attr3: {
type: 'string',
},
},
}
const schema2: JSONSchemaType<Mine> = {
type: 'object',
properties: {
attr1: {
type: 'number',
nullable: true,
},
attr2: {
type: 'object',
patternProperties: {
'^.+$': { type: 'string' },
},
propertyNames: {
pattern: '^[a-zA-Z0-9_-]+$',
},
// required: [], // missing
},
attr3: {
type: 'string',
},
},
}
const schema3: JSONSchemaType<Mine> = {
type: 'object',
properties: {
attr1: {
type: 'number',
nullable: true,
},
attr2: {
type: 'object',
patternProperties: {
'^.+$': { type: 'string' },
},
propertyNames: {
pattern: '^[a-zA-Z0-9_-]+$',
},
// nullable: true, // missing
required: [],
},
attr3: {
type: 'string',
},
},
required: ['attr1', 'attr2', 'attr3'], // my error, forgotten and left behind
}
const schema4: JSONSchemaType<Mine> = {
type: 'object',
properties: {
attr1: {
type: 'number',
nullable: true,
},
attr2: {
type: 'object',
patternProperties: {
'^.+$': { type: 'string' },
},
propertyNames: {
pattern: '^[a-zA-Z0-9_-]+$',
},
nullable: true,
required: [],
},
attr3: {
type: 'string',
// nullable: true, // missing
},
},
required: ['attr1', 'attr2', 'attr3'], // my error, forgotten and left behind
}
const schema5: JSONSchemaType<Mine> = {
type: 'object',
properties: {
attr1: {
type: 'number',
nullable: true,
},
attr2: {
type: 'object',
patternProperties: {
'^.+$': { type: 'string' },
},
propertyNames: {
pattern: '^[a-zA-Z0-9_-]+$',
},
nullable: true,
required: [],
},
attr3: {
type: 'string',
nullable: true,
},
},
// ERROR: contadiction with nullable
required: ['attr1', 'attr2', 'attr3'], // my error, forgotten and left behind
}
const schema6: JSONSchemaType<Mine> = {
type: 'object',
properties: {
attr1: {
type: 'number',
nullable: true,
},
attr2: {
type: 'object',
patternProperties: {
'^.+$': { type: 'string' },
},
propertyNames: {
pattern: '^[a-zA-Z0-9_-]+$',
},
nullable: true,
required: [],
},
attr3: {
type: 'string',
nullable: true,
},
},
required: [], // fixed
} |
I landed on this issue with a similar error from the TypeScript compiler. VERSIONS: $ npx tsc --version
Version 5.4.4
$ jq .version node_modules/ajv/package.json
"8.12.0" I was able to make a simple fix for the problem after a lot of reading and puzzling over different things. The JSON schema is written in YAML format, then compiled to both a JSON format schema and to a TypeScript interface using quicktype. import { promises as fsp } from 'node:fs';
import Ajv, { JSONSchemaType } from "ajv";
const ajv = new Ajv.default({
// strict: true,
// allowUnionTypes: true,
// validateFormats: true
});
import { OperatingCosts } from '../types-evchargingspec/operating-costs.js';
import _schema from '../schemas/operating-costs.json' with { type: "json" };
const schema: JSONSchemaType<OperatingCosts> = _schema.definitions.OperatingCosts; This imports the generated type and -- since the generated schema is in JSON format, I thought why not use an import statement. But, that gave error messages similar to the above. The fix is: import { promises as fsp } from 'node:fs';
import Ajv, { JSONSchemaType } from "ajv";
const ajv = new Ajv.default({
// strict: true,
// allowUnionTypes: true,
// validateFormats: true
});
import { OperatingCosts } from '../types-evchargingspec/operating-costs.js';
// import _schema from '../schemas/operating-costs.json' with { type: "json" };
const _json = await fsp.readFile('../schemas/operating-costs.json', 'utf-8');
const _schema = JSON.parse(_json);
const schema: JSONSchemaType<OperatingCosts> = _schema.definitions.OperatingCosts; That is, do not use |
What version of Ajv are you using? Does the issue happen if you use the latest version?
8.0.1, yes
Ajv options object
Your code
What results did you expect?
Should be a valid type, instead the compiler throws
TS2322: Type '{ type: "object"; properties: { other: { type: "boolean"; }; some: { type: "string"; }; }; }' is not assignable to type 'JSONSchemaType<test, false>'. Type '{ type: "object"; properties: { other: { type: "boolean"; }; some: { type: "string"; }; }; }' is not assignable to type '{ oneOf: readonly JSONSchemaType<test, false>[]; } & { [keyword: string]: any; $id?: string; $ref?: string; $defs?: { [x: string]: JSONSchemaType<Known, true>; }; definitions?: { ...; }; }'. Property 'oneOf' is missing in type '{ type: "object"; properties: { other: { type: "boolean"; }; some: { type: "string"; }; }; }' but required in type '{ oneOf: readonly JSONSchemaType<test, false>[]; }'
The text was updated successfully, but these errors were encountered: