Thank you for the wonderful work of joi! I am currently using it to validate API payloads for express and loving it. :heart:
It's not a new problem.
As I'm writing my server in TypeScript, I'm trying to figure out a way to not duplicate the constraint information that is shared between TypeScript and joi.
Other solutions such as this exist. However, I'd like to propose a TypeScript-only solution, which would require joi's type annotation to change.
The API is purely in the realm of TypeScript annotation. There is no new JavaScript API.
import * as joi from 'joi';
const mySchema = joi.object(
{
foo : joi.string(),
bar : joi.number.required()
}).required();
// yields { foo?: string, bar : number }
type ValueType = joi.ValidatedValueType<typeof mySchema>
const result = mySchema.validate( { bar : 123 } /* accepts type any */ );
Where
result.value
if !result.error, is of type joi.ValidatedValueType<typeof mySchema>, i.e. { foo?: string, bar: number }.
Use TypeScript's more recent advanced type operations such as conditional types, mapped types, and type inferences. Here's a draft implementation supporting extraction of string, number and boolean, and a (failed) attempt at object.
import * as joi from "joi";
type ValidatedValueType<T extends joi.Schema> = T extends joi.StringSchema
? string
: T extends joi.NumberSchema
? number
: T extends joi.BooleanSchema
? boolean
: T extends joi.ObjectSchema ? ValidatedObjectType<T> :
/* ... more schemata ... */ never;
Where I'm stuck is at ValidatedObjectType, because joi's current type definition of joi.ObjectSchema is not a generic over its keys, so I am unable to infer the key schema in the case of static keys.
If ObjectSchema was a generic like ObjectSchema<TShape extends joi.SchemaMapping>, it is possible to use TypeScript's mapped types to recursively extract property type from the matching schema. Something along the lines of:
type ValidatedObjectType<T extends ObjectSchema<any>> =
T extends ObjectSchema<infer TShape>
? { [ key in keyof TShape ] : ValidatedValueType<TShape[key]> } // recursively extracts type from schema
: never
I have a gut feeling that the constraints expressible by joi is a strict superset of the constraints expressible by TypeScript, and that TypeScript provides enough type operations for the inference needed for the conversion. This however remains to be validated.
Yes with some help on implementation and further thoughts on how to handle complex object schemata where keys are not static e.g. regex-based.
Hi!
Although I'm currently thinking about owning that and making it part of the project, TypeScript definitions are currently hosted on https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/joi/ so you might have better luck there.
What you said is just gibberish to me as I'm not fluent at all in TypeScript but I'm still interested in the outcome of that discussion. I'll keep it open, you can do problem solving here, hopefully someone more knowledgeable than me will step up.
Good to know, thanks! I can try and see what I can do and perhaps PR directly to DefinitelyTyped?
@winhillsen did you managed to define the type extraction method?
I am porting an application full of Joi schemas to typescript. These method would instantly make all my services handlers typed arguments.
Also, I am willing to help if it is needed.
Thanks!
@TCMiranda I unfortunately got swamped by other things in the meantime, but this is a good incentive to come back to this one. I'll ping you by email!
Hey @winhillsen, that's awesome. Reach me at tgcvmr@gmail if you want to
talk about it. I may have time to look into this tomorrow but I probably
have to study a little to understand what is possible and what isn't.
On Mon, Oct 1, 2018, 07:25 winhillsen notifications@github.com wrote:
@TCMiranda https://github.com/TCMiranda I unfortunately got swamped by
other things in the meantime, but this is a good incentive to come back to
this one. I'll ping you by email!—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/hapijs/joi/issues/1557#issuecomment-425859628, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AJnE_2kS7cLjS1V1xQ7_yoHBUVQZyQb2ks5uge2vgaJpZM4VuHeP
.
So, one thing that I realized that solves the issue with ArraySchema and could improve Object's one:
// if the ArraySchema holds the extracted type, I don't need to recursively extract it below
export interface ArraySchema<T = any> extends AnySchema {
items<T>(type: T): ArraySchema<extractType<T>>;
}
// updated utility
export type extractType<T extends SchemaLike> =
T extends BooleanSchema ? boolean :
T extends StringSchema ? string :
T extends ObjectSchema<infer O> ? { [K in keyof O]: extractType<O[K]> } :
T extends ArraySchema<infer O> ? O[] :
// no recursive call here
any;
// then...
const roles = Joi.array().items(Joi.string());
type extractArray = Joi.extractType<typeof roles>;
export const extactedArray: extractArray = ['admin'];
So the way to do it may be to always hold the extracted type into complex schemas, like ArraySchema.
@winhillsen I think that the strategy I explained at the comment above solves our use case.
So I created a repo to work at it aside of Joi: joi-extract-type
Using Module Augmentation it was possible to create the utility boxed inside that library, that patches only the necessary interfaces to allow extracted types to be stored inside the Schemas. Following recommendations from DefinitelyTyped: How can I contribute?:
Test
Before you share your improvement with the world, use it yourself.
Ill probably finish all simple use cases soon, if you want, feel free to contribute there. Eventually, if the library proves to solve the issue I can purpose to merge it inside Joi definitions.
cool!
I'm using https://github.com/gcanti/io-ts that is tailored for typescript but Joi when and otherwise is a super feature. Achieve that in Typescript with Algebric Data Types would be a real charm!
@TCMiranda god speed buddy
@Marsup can you get a look at joi-extract-type and give me your opinion if it makes sense to purpose a merge with Joi's types? I'm pretty confident that it works. But Module Augmentation limits the method and prevents it to change return types, for example.
Thanks a lot @TCMiranda. Any plan to merge it in official types?
Yeah, I would definitely love to have the tool within the official types. But anyway I was looking for some advice from Joi's collaborators before working on it.
As @Marsup said, the definitions could be part of the main project:
Although I'm currently thinking about owning that and making it part of the project, TypeScript definitions are currently hosted on https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/joi/ so you might have better luck there.
I could help it to move towards this.
I don't want to hijack this issue since this is a different topic, but I have tried to include definitions without much luck, it's in the ts branch. Having talked to someone at Microsoft, the way I want to implement the definitions is a design limitation of typescript and appears to be impossible. If you want to discuss this with me it may be best to do it in another issue or ping me on hapi's slack if you're already there.
Would love to see this get implemented. Currently building out an API with a lot of repetitive properties...
Hey guys, I have created another repo doing the similar thing: hjkcai/typesafe-joi.
I rewrote the core of the original joi type definition. It covers more joi APIs in TypeScript form. For example:
// results { a?: number, b: string } | false
Joi.object({ a: Joi.number() }).keys({ b: Joi.string().required() }).allow(false)
However, IMO I choose to use it as a standalone library because limitations exist: Joi's schemas cannot be perfectly defined in TypeScript. Forking it might be a better choice.
@TCMiranda and @hjkcai I have wanted to do something similar for a while, I just published my types to https://github.com/maghis/types-joi
@hjkcai just found this thread and your implementation, I realized we took slightly different approaches.
Personally I'm waiting for a few typescript features before attempting a PR to the official @types
The problem is that the implementation in DT is using type to enforce the input to the validation and merging "proper" types will break (or actually fix) a lot of code that depends on them.
@hueniverse any reason for closing this issue? Is it because this should be fixed in the types library? This seems still unresolved for now and a key missing feature for typescript users
I closed it because we are not going to be fixing the existing @types definitions or track that effort here. Adding native types support to joi is on the list as it is a hapijs module. I don't need open issues to track it, as we don't have such open issues on any other hapi module.
Most helpful comment
@winhillsen I think that the strategy I explained at the comment above solves our use case.
So I created a repo to work at it aside of Joi: joi-extract-type
Using Module Augmentation it was possible to create the utility boxed inside that library, that patches only the necessary interfaces to allow extracted types to be stored inside the Schemas. Following recommendations from DefinitelyTyped: How can I contribute?:
Ill probably finish all simple use cases soon, if you want, feel free to contribute there. Eventually, if the library proves to solve the issue I can purpose to merge it inside Joi definitions.