-
Notifications
You must be signed in to change notification settings - Fork 327
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
0.2.0 #27
Conversation
What is the reason for removing It seems like |
Also can you, please, describe reasons behind revert pruning properties? |
With respect to "optional" fields (returned by an API) there are 2 main use cases I can think of
type Payload = {
foo: string | null
} Now if you extract the static type from a
type Payload = {
foo?: string
} This should be covered by the new const RTPayload = t.partial({ foo: t.string })
type Payload = TypeOf<typeof RTPayload> // same as { foo?: string } However
I agree with you, we can remove the
This is my fault, I misinterpreted the way TypeScript handles strictness, let's see some examples const x: { a: string } = { a: 's', b: 1 } // error is an error but only because I'm using an object literal AND the option // suppressExcessPropertyErrors = true
const x: { a: string } = { a: 's', b: 1 } // NO error const y = { a: 's', b: 1 }
const x: { a: string } = y // NO error const y = { a: 's', b: 1 }
declare function f(x: { a: string }): void
f(y) // NO error |
Added a new combinator: |
b6fb864
to
7d0293f
Compare
Removed export function maybe<RT extends t.Any>(type: RT, name?: string): t.UnionType<[RT, typeof t.null], t.TypeOf<RT> | null> {
return t.union([type, t.null], name)
} Another interesting combinator in user land might be The problem const payload = {
celsius: 100,
fahrenheit: 100
}
const Payload = t.interface({
celsius: t.number,
fahrenheit: t.number
})
// x can be anything
function naiveConvertFtoC(x: number): number {
return (x - 32) / 1.8;
}
// typo: celsius instead of fahrenheit
console.log(t.validate(payload, Payload).map(x => naiveConvertFtoC(x.celsius))) // NO error :( Solution (branded types) export function brand<T, B extends string>(type: t.Type<T>, brand: B): t.Type<T & { readonly __brand: B }> {
return type as any
}
const Fahrenheit = brand(t.number, 'Fahrenheit')
const Celsius = brand(t.number, 'Celsius')
type CelsiusT = t.TypeOf<typeof Celsius>
type FahrenheitT = t.TypeOf<typeof Fahrenheit>
const Payload2 = t.interface({
celsius: Celsius,
fahrenheit: Fahrenheit
})
// narrowed types
function convertFtoC(fahrenheit: FahrenheitT): CelsiusT {
return (fahrenheit - 32) / 1.8 as CelsiusT;
}
console.log(t.validate(payload, Payload2).map(x => convertFtoC(x.celsius))) // error: Type '"Celsius"' is not assignable to type '"Fahrenheit"'
console.log(t.validate(payload, Payload2).map(x => convertFtoC(x.fahrenheit))) // ok |
Actually it looks like there is no much need to define maybe in the user space. Just inlining could work fine. t.object({
foo: t.string,
bar: t.union([t.null, t.undefined, t.number])
}) Regarding pruning I still don't think there is much necessity in removing it. It's much easier to change behaviour to non-prune in the future since it's non-breaking change, while much harder to do otherwise from the perspective of library consumers. I'd better keep pruning for now and see if people will create issue around it. Then one can see their usecases and make decision based on them. Also I guess it be quite useful if you can come up with the definition of what low level means from your point of view. For example |
Yes, I just wanted to show how to define a custom combinator (if you define many maybe types a custom combinator is helpful in order to be DRY)
The thing is that pruning was a way to align io-ts to TypeScript's behavior. But I was mistaken with respect to strictness, so is the current version that doesn't align with ts and should be fixed. Also without pruning, intersections are easier to implement and they are more general.
The extracted static types are different const T1 = t.interface({
foo: t.union([t.undefined, t.string])
})
type TT1 = t.TypeOf<typeof T1>
/* same as
type TT1 = {
foo: string | undefined;
}
*/
const x1: TT1 = { foo: undefined } // ok
const x2: TT1 = {} // error
const T2 = t.partial({
foo: t.string
})
type TT2 = t.TypeOf<typeof T2>
/* same as
type TT2 = {
foo?: string; <= note the ? here
}
*/
const x3: TT2 = { foo: undefined } // ok
const x4: TT2 = {} // <= NO error Without the This is especially useful when using react import * as React from 'react'
const RuntimeProps = t.interface({
foo: t.union([t.undefined, t.string])
})
type Props = t.TypeOf<typeof RuntimeProps>
class MyComponent extends React.Component<Props, void> {}
<MyComponent /> // error: Property 'foo' is missing in type... while using import * as React from 'react'
const RuntimeProps = t.partial({
foo: t.string
})
type Props = t.TypeOf<typeof RuntimeProps>
class MyComponent extends React.Component<Props, void> {}
<MyComponent /> // NO error |
What if I have following type interface Test {
foo: string,
bar?: number,
} How one would create this with partial? Maybe I just missed something, sorry :) |
With an intersection const T1 = t.interface({
foo: t.string
})
const T2 = t.partial({
bar: t.number
})
const Test = t.intersection([T1, T2])
type TestT = t.TypeOf<typeof Test>
const x1: TestT = { foo: 'a' } // ok |
Ah, ok, I see now 👍 |
@gyzerok published a preview in the |
@gcanti thank you! Won't be able to test it right away, but expecting to try upgrade in our project in the following days. |
@gyzerok btw, have you some source schema for the API payloads? Something like JSON Schema, swagger, etc...? |
@gcanti not really, mostly textual documentation :) |
partial
combinator (makes optional props possible)readonly
combinator (values are not frozen in production)never
typemaybe
combinator, can be defined in userland aspathReporterFailure
function from default reportersintersection
combinator accepting onlyInterfaceType
s