-
Notifications
You must be signed in to change notification settings - Fork 328
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
Option fields: intersection + partial vs union with undefined #415
Comments
Theoretically no import * as t from 'io-ts'
const V1 = t.intersection([
t.type({
foo: t.string
}),
t.partial({
bar: t.string
})
])
const V2 = t.type({
foo: t.string,
bar: t.union([t.string, t.undefined])
})
type V1 = t.TypeOf<typeof V1>
/*
type V1 = {
foo: string;
bar?: string | undefined;
}
*/
type V2 = t.TypeOf<typeof V2>
/*
type V2 = {
foo: string;
bar: string | undefined;
}
*/ ^ the In practice yes they are equivalent, if you use
V1.encode({ foo: 'foo' }) // you can omit `bar`
V2.encode({ foo: 'foo', bar: undefined }) |
You could do some type magic to fix the required key issue, ex: /**
* Type lambda returning a union of field names from input type A having type P
* Inspired by https://stackoverflow.com/questions/55247766/check-if-an-interface-has-a-required-field
*/
type FieldsWith<A, P> = { [K in keyof P]-?: (A extends P[K] ? K : never) }[keyof P]
/**
* Dual for FieldsWith - returns the rest of the fields
*/
type FieldsWithout<A, P> = Exclude<keyof P, FieldsWith<A, P>>
/**
* Typa lambda returning new type with all fields within P having type U marked as optional
*/
type MakeOptional<P, U = undefined> = Pick<P, FieldsWithout<U, P>> & Partial<Pick<P, FieldsWith<U, P>>>
/**
* Fix signature by marking all fields with undefined as optional
*/
const fixOptionals = <C extends t.Mixed>(c: C): t.Type<MakeOptional<t.TypeOf<C>>, t.OutputOf<C>, t.InputOf<C>> => c
/**
* Just an alias for T | undefined codec
*/
const optional = <C extends t.Mixed>(c: C): t.Type<t.TypeOf<C> | undefined, t.OutputOf<C>, t.InputOf<C>> =>
t.union([t.undefined, c])
/**
* Example type
*/
const Profile = fixOptionals(t.type({
name: t.string,
age: optional(t.number)
}))
type Profile = t.TypeOf<typeof Profile>
/**
* the type can now be initialized ignoring undefined fields
*/
const someProfile: Profile = { name: "John" }
Profile.encode(someProfile) // Ok
Profile.encode({ }) // Not ok
console.log(Profile.decode(someProfile))
// prints:
//
// right({
// "name": "John"
// }) This topic was pretty often discussed within several issues, maybe we can add some support to the core library, the optional field syntax seems prettier for me too, but the solution above also adds some more verbosity to type hints. |
Thank you both for the thoughtful response. |
See #266 |
📖 Documentation
I am familiar with Mixing required and optional props and define my objects like:
However, I had a colleague define it like:
Are these equivalent or is one preferred over the other?
The text was updated successfully, but these errors were encountered: