Skip to content

Instantly share code, notes, and snippets.

@odiak
Created September 22, 2021 02:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save odiak/725cd4a2745d4751541730b1681160f0 to your computer and use it in GitHub Desktop.
Save odiak/725cd4a2745d4751541730b1681160f0 to your computer and use it in GitHub Desktop.
import * as t from "io-ts";
const codec = t.type({
a: t.number,
b: t.union([t.string, t.undefined]),
});
console.log(codec.decode({ a: 1 }));
type codec = t.TypeOf<typeof codec>;
// --------------------
class Optional<A> extends t.Type<A | undefined> {
constructor(tp: t.Type<A>) {
super(
"Optional",
(u): u is A | undefined => u === undefined || tp.is(u),
(u, c) => (u === undefined ? t.success(undefined) : tp.validate(u, c)),
(a) => a
);
}
}
const optional = <A>(tp: t.Type<A>) => new Optional(tp);
type PresentPropKeys<P extends t.Props, K = keyof P> = K extends string
? P[K] extends Optional<infer _>
? never
: K
: never;
type OptionalPropKeys<P extends t.Props, K = keyof P> = Exclude<
K,
PresentPropKeys<P>
>;
type MyInterface<P extends t.Props> = t.IntersectionC<
[
t.TypeC<Pick<P, PresentPropKeys<P>>>,
t.PartialC<Pick<P, OptionalPropKeys<P>>>
]
>;
const typeWithOptional = <P extends t.Props>(props: P): MyInterface<P> => {
const presentProps: t.Props = {};
const optionalProps: t.Props = {};
for (const [key, value] of Object.entries(props)) {
if (value instanceof Optional) {
optionalProps[key] = value;
} else {
presentProps[key] = value;
}
}
return t.intersection([
t.type(presentProps as Pick<P, PresentPropKeys<P>>),
t.partial(optionalProps as Pick<P, OptionalPropKeys<P>>),
]);
};
const codec2 = typeWithOptional({
a: t.number,
b: optional(t.string),
});
console.log(codec2.decode({ a: 1, b: undefined }));
type codec2 = t.TypeOf<typeof codec2>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment