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

Discussion: Can oneOf be implemented? #151

Closed
rubenferreira97 opened this issue Feb 4, 2022 · 2 comments
Closed

Discussion: Can oneOf be implemented? #151

rubenferreira97 opened this issue Feb 4, 2022 · 2 comments

Comments

@rubenferreira97
Copy link

rubenferreira97 commented Feb 4, 2022

Currently is not possible to write a schema, where the given data must be valid against exactly one of the given subschemas.

I am almost certain that this was not implemented since it's hard to express on Typescript.

However, after some search I think it's possible to achieve a type "XOR" by adding never to the remaining properties (Don't know if it solves every problem though).

Playground from microsoft/TypeScript#14094:

@rubenferreira97 rubenferreira97 changed the title Discussion: Discussion: Can oneOf be implemented? Feb 4, 2022
@sinclairzx81
Copy link
Owner

@rubenferreira97 Hi, thanks for the suggestion.

I'm not sure if this is something that could be implemented in TypeBox (at least not as a core primitive type). The preference for TypeBox is to only implement basic primitives that can be composed together to form higher kinded types. So the preference would be to provide a sufficient set of core primitives in TypeBox to allow the composition of a XOR type rather than implementing XOR as a primitive.

Looking at your example tho

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends Object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;

type AllXOR<T extends any[]> =
    T extends [infer Only] ? Only :
    T extends [infer A, infer B, ...infer Rest] ? AllXOR<[XOR<A, B>, ...Rest]> :
    never;

So to implement this in a way that aligns with the goals of TypeBox, TypeBox would need to implement both conditional types as well as schema inference for infer. Both of which would require appropriate JSON schema representations to be composed along the way.

Conditional type mapping is something I would like to explore in TypeBox one day, however implementing an T extends U ? true : false conditional type would require TypeBox to carry out similar type comparison checks over JSON schema and ensure they match the extends rules of TypeScript. The API would look as follows.

const T = Type.Extends(LeftType, RightType, TrueType, FalseType)

// T = Type.Literal(true)
const T = Type.Extends(Type.String(), Type.String(), Type.Literal(true), Type.Literal(false))

// T = Type.Literal(false)
const T = Type.Extends(Type.String(), Type.Number(), Type.Literal(true), Type.Literal(false))

Unfortunately, the logic to evaluate the extends expression adds a significant amount of code to the codebase, so it's not something I'm planning on looking into in the near future. There is a desire however to include the utility types Type.Extract() and Type.Exclude(), but implementing these are dependent on the conditional type mapping provided by Type.Extends().

Happy to discuss things more, particularly if you have a draft implementation on how the above AllXOR type could be implemented through the TypeBox primitive functions.

@sinclairzx81
Copy link
Owner

@rubenferreira97 Hi, just a follow up on this issue.

I have explored implementing conditional mapping (including support for Extract and Exclude utility types) in TypeBox (which you can find a reference implementation here https://github.com/sinclairzx81/sidewinder/blob/extends/libs/type/extends.ts). However the implementation and complexity to write and test against TypeScript's structural checker was a bit too top heavy for the library, so has been omitted from the latest release.

I may investigate this functionality again in future, but for now will be putting on the back burner (until such time as the TypeScript type system has settled down a bit more)

Will close off this issue for now.
Many Thanks
S

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants