-
Notifications
You must be signed in to change notification settings - Fork 329
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
Make optional fields more user friendly #542
Comments
Any update on this? Would be very helpful. |
The only "elegant" way I found recently is to use import * as t from 'io-ts'
import { optionFromNullable } from 'io-ts-types'
const Foo = t.type({
foo: t.string,
baz: optionFromNullable(t.string)
})
type Foo = t.TypeOf<typeof Foo> // { foo: string, baz: O.Option<string> }
console.log(Foo.decode({ foo: 'foo' })) // right({ foo: 'foo', baz: none })
console.log(Foo.decode({ foo: 'foo', baz: 'baz' })) // right({ foo: 'foo', baz: some('baz') })
|
After being inspired by sparceType from io-ts-extra I've implemented similar thing for Decoder: import * as D from "io-ts/Decoder";
import { pipe } from "fp-ts/lib/function";
import { partition } from "fp-ts/lib/Record";
type AnyDecoder = D.Decoder<unknown, unknown>;
type Props = { [K in string]: AnyDecoder & Partial<Optional> };
export interface Optional {
optional: true;
}
const isOptional = <T>(val: T & Partial<Optional>): val is T & Optional => {
return val.optional ?? false;
};
type OptionalKeys<Base> = {
[Key in keyof Base]: Base[Key] extends Optional ? Key : never;
}[keyof Base];
type RequiredKeys<Base> = {
[Key in keyof Base]: Base[Key] extends Optional ? never : Key;
}[keyof Base];
type Sparse<P> = {
[K in RequiredKeys<P>]: D.TypeOf<P[K]>;
} &
{
[K in OptionalKeys<P>]?: D.TypeOf<P[K]>;
};
/**
* Marks decoder as an `Optional`, intended to be used with `D.sparse`.
*
* @see sparse
*/
export const optional = <D extends AnyDecoder>(decoder: D): D & Optional => {
return Object.assign({}, decoder, { optional: true as const });
};
/**
* Combines `D.struct` and `D.partial` in a nice way where instead of:
* ```ts
* const Person = pipe(
* D.struct({ name: D.string }),
* D.intersect(D.partial({ age: D.number }))
* )
* ```
*
* You can do:
* ```ts
* const Person = sparse({
* name: D.string,
* age: optional(D.number),
* })
* ```
*
* While having a great type inference:
* ```ts
* // const: Person: D.Decoder<unknown, {
* // name: string;
* // age?: number | undefined;
* // }>
* })
* ```
*/
export const sparse = <P extends Props>(
props: P
): D.Decoder<unknown, { [K in keyof Sparse<P>]: Sparse<P>[K] }> => {
const partitioned = pipe(props, partition(isOptional));
return pipe(
D.struct(partitioned.left),
D.intersect(D.partial(partitioned.right))
) as any;
}; Also, It has really nice type inference. Let me know if this is desired and will make PR to add this /cc @gcanti Downside is that when you hover over the |
Worth noting that it encodes to |
I think I have a simplistic const optionalD: <I, A>(or: d.Decoder<I, A>) => d.Decoder<undefined | I, O.Option<A>> = or =>
({ decode: i => i === undefined ? E.right(O.none) : pipe(i, or.decode, E.map(O.some)) })
const optionalE: <I, A>(or: e.Encoder<I, A>) => e.Encoder<undefined | I, O.Option<A>> = or =>
({ encode: flow(O.map(or.encode), O.toUndefined) })
const optionalC = <I, O, A>(codec: c.Codec<I, O, A>) => c.make(optionalD(codec), optionalE(codec)) |
I added a PR to address this: #654 |
Other decoding/validation libraries have more user friendly optionality support like this:
Would be great to have something like this here too. As discussed with @gcanti there was some issues with the stable API #140 #266 but he doesn't remember if he even tried this with the experimental API.
The text was updated successfully, but these errors were encountered: