-
-
Notifications
You must be signed in to change notification settings - Fork 172
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 ajv-spec benchmarks #256
Conversation
Linking draft implementation of TS > JSON Schema representation mapping on repl.it using the Could be good to compare the Wondering if specifically Hope this helps! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for contributing. I will refactor it sometimes later and try to enhance json schema generator following your guide.
@samchon All good :) |
Anyway, is it really okay that defining optional only instead of using undefined type? As you know, TypeScript can assigned undefined value on the question marked property type like below. With interface ISomething {
name: string;
etc?: string;
}
const name: string = "unknown";
const etc: string = "another";
const something: ISomething = {
name,
etc: name === "unknown" ? undefined : etc,
}; |
Yes, it's fine. TypeScript will treat // -------------------------------------------------------------------
// { prop: undefined | null | number } means:
// - prop key MUST exist
// - prop value must be undefined | null | number
prop: Type.Union([Type.Undefined(), Type.Null(), Type.Number()])
{ prop: undefined } // ok
{ prop: null } // ok
{ prop: 1 } // ok
{ } // fail !!
// -------------------------------------------------------------------
// { prop?: null | number } means:
// - prop key can be OPTIONAL
// - prop value must be null | number (or undefined)
prop: Type.Optional(Type.Union([Type.Null(), Type.Number()]))
{ prop: undefined } // ok - see below for details.
{ prop: null } // ok
{ prop: 1 } // ok
{ } // ok Note: The reason const value = {}
if(value.prop === undefined) console.log('ok') So to support missing properties, the correct way to express this is via Hope that helps. |
Just on this...
The So for example. // ------------------------------------------------------
// TypeBox
// ------------------------------------------------------
const T = Type.Object({
prop: Type.Optional(Type.Union([Type.String(), Type.Null()])
// ^ string | null | undefined
})
// ------------------------------------------------------
// JSON Schema
// ------------------------------------------------------
const T = {
type: 'object',
properties: {
prop: { // <------ additionalProperties: false checks Object.keys(T.properties)
anyOf: [
{ type: 'string' },
{ type: 'null' }
]
}
}
}
// ------------------------------------------------------
// additionalProperties: false test
// ------------------------------------------------------
const allowed = Object.keys(T.properties)
Object.keys({ prop: null }).every(key => allowed.includes(key)) // ok
Object.keys({ }).every(key => allowed.includes(key)) // ok
Object.keys({ foo: 1 }).every(key => allowed.includes(key)) // fail Constrained schemas (such as the ones implemented in TSON) will likely need to control minimum required and excess properties checks through a combination of Again, hope this helps! |
How about this case that assigning undefined value on non-declared property? To anticipate below case, I've implemented such logic because following JS rule, undefined valued property and real undefined property are equivalent. When undefined value comes{
x: 1,
y: 1,
z: 1,
excess: undefined,
} TypeBoxreturn function check(value) {
return (
(typeof value === 'object' && value !== null && !Array.isArray(value)) &&
(Object.keys(value).length === 3) &&
(typeof value.x === 'number') &&
(typeof value.y === 'number') &&
(typeof value.z === 'number')
)
} TSON(input, path, exceptionable) =>
"number" === typeof input.x &&
"number" === typeof input.y &&
"number" === typeof input.z &&
Object.entries(input).every(([key, value]) => {
if (undefined === value) return true;
if (["x", "y", "z"].some((prop) => key === prop))
return true;
return false;
}), |
JSON Schema requires you to be explicit about allowing (or disallowing) additionalProperties. The default is to allow them. const T = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number()
})
{ x: 1, y, 2, z: 3 } // ok
{ x: 1, y, 2, z: 3, excess: undefined } // ok const T = Type.Object({
x: Type.Number(),
y: Type.Number(),
z: Type.Number()
}. { additionalProperties: false })
{ x: 1, y, 2, z: 3 } // ok
{ x: 1, y, 2, z: 3, excess: undefined } // fail |
I mean that the You suggested that That is the case what I've seen in field. Of course, below implementation seems really terrible, but it has been introduced as an exemplary way for developing backend program composing union typed data (may be from DB?). Of course, whether supporting below terrible case or not is totally up to your policy. But while looking your solution const value: A|B|C = {
id: "a",
name: "b",
something: "c",
...{
...properties of A with undefined value
},
...{
...B,
},
...{
...C
}
}; |
Sure, but it's not superfluous because a property with an Object.keys({ x: 1, y: undefined }) // ['x', 'y'] - 'y' is unexpected
Undefined in JavaScript is a little ambiguous, as it could mean "the key does not exist" or "the key exists but it's value is undefined". I expect this is another reason why JSON schema doesn't have a specification for const A = {}
const B = { prop: undefined }
if(A.prop === undefined) console.log('ok')
if(B.prop === undefined) console.log('also ok')
// ^ as a user, you wouldn't know by running this test that `A` had a missing `prop` key. The user would
// need to explicitly call `Object.keys(A | B)` to determine if the property actually existed. This is a problem
// if there is dependent logic based on the property keys. The JSON Schema specification provides
// assurances here, but it does so by way of NOT having a specification for undefined. Overall, I do think it's important to have a distinction between |
Within framework of JSON schema, your policy seems right. How to handle undefined property, it would be just a difference between our libraries. Thanks for detailed description. |
This PR includes an additional
ajv-spec
benchmark. It is offered to provide a structural comparison between the schemas that are generated viaTSON.application<[...], 'ajv'>
(which are not working for several existing tests).This benchmark uses the same schematics as the TypeBox benchmark but instead measures the
Ajv
compiler. Theajv-spec
benchmark also uses the same spoiler data that was implemented for the TypeBox benchmark to ensure parity. All tests are currently passing (however these have only been implemented for theis
benchmark)This PR also updates the
TypeBoxObjectUnionImplicit
to useType.Optional()
overType.Union([Type.Undefined(), ...])
to permit missing properties as per spoiler data.Results below.
Submitting for comparative benchmarking and review.