Optional doesn't work with Tuple #1173
Replies: 2 comments
-
@aleclarson Hi,
Yes, I agree. Unfortunately there is no way to represent Tuple element optionality in Draft 7. There is a way in Draft 2020-12, but downstream validator support is questionable. The following is the best representation I've been able to get working in // ------------------------------------------------------------------
// Example
// ------------------------------------------------------------------
import { Type, Static } from '@sinclair/typebox'
import Ajv from 'ajv/dist/2020'
type Tuple = Static<typeof Tuple> // type Tuple = [string, (number | undefined)?] - set exactOptionalTypes to remove undefined
const Tuple = TupleNext([Type.String(), Type.Optional(Type.Number())])
// strict mode: "prefixItems" is 2-tuple, but minItems or maxItems/items are not specified or different at path "#"
console.log((new Ajv()).validate(Tuple, [])) // false
console.log((new Ajv()).validate(Tuple, ['hello'])) // true
console.log((new Ajv()).validate(Tuple, ['hello', 42])) // true
console.log((new Ajv()).validate(Tuple, ['hello', 'world'])) // false
// ------------------------------------------------------------------
// Reference: TupleNext<...>
// ------------------------------------------------------------------
import * as t from '@sinclair/typebox'
type StaticTupleNext<Types extends t.TSchema[], Result extends unknown[] = []> = (
Types extends [infer Left extends t.TSchema, ...infer Right extends t.TSchema[]]
? (
Left extends t.TOptional<t.TSchema>
? StaticTupleNext<Right, [...Result, t.Static<Left>?]>
: StaticTupleNext<Right, [...Result, t.Static<Left>]>
) : Result
)
export interface TTupleNext<Types extends t.TSchema[]> extends t.TSchema {
[t.Kind]: 'TupleNext'
type: 'array'
static: StaticTupleNext<Types>
prefixItems: Types
}
function TupleNext<Types extends t.TSchema[]>(prefixItems: [...Types]): TTupleNext<Types> {
// minItems is the total of non-optional properties. expects last element to be optional.
const minItems = prefixItems.reduce((result, type) => t.KindGuard.IsOptional(type) ? result : result + 1, 0)
const maxItems = prefixItems.length
const items = true
return t.CreateType({ [t.Kind]: 'TupleNext', type: 'array', prefixItems, items, minItems, maxItems }) as never
} Advancements to the Tuple are at an impasse, and sadly it's been this way for a long time. Basically, I want TypeBox to support both Optional Try run the above example and comment out the import Ajv from 'ajv' // 'ajv/dist/2020' -- comment out the dist/2020 and observe exceptions thrown So, this is where things are at. It is technical possible for TypeBox to implement this functionality internally (Check/TypeCompiler), but doing so would render Tuple incompatible with most Ajv installations (everyone is on Draft 7). At this point in time, TypeBox doesn't provide a full blown spec compliant validator (it only validates a subset of json schema) so can't be substituted for Ajv ... but given that TS alignment is being hindered by spec implementation details, it begs the following question.
For what it's worth, I've considered it, but I would probably want to seek funding for the work. Anyway, Let me know your thoughts. |
Beta Was this translation helpful? Give feedback.
-
@aleclarson Hiya, Let's move this issue to a discussion thread. As noted, it's not really feasible for TypeBox to implement optional Tuple elements under the current draft specification (Draft 7), but I do want this functionality to be possible in future. As mentioned, many of the problems here relate to Ajv's implementation of the 2020 draft, mixed with an ecosystem bound to Draft 7, mixed with schema incompatibilities that make moving from Draft 7 schematics to Draft 2020 infeasible for many Ajv users (which is an issue that extends outside of TypeBox's control). Here are some possible paths to take:
I'll shift this issue to a discussion for now, but am keen to discuss things more. |
Beta Was this translation helpful? Give feedback.
-
In this example,
type MyTuple
equals[number, number]
when it should be[number, number?]
.Although
Type.Optional
is intended to be used with object properties only, I think it's reasonable to expect it to work with tuple types, too, since the?
token is used by TypeScript to symbolize an optional element.Beta Was this translation helpful? Give feedback.
All reactions