io-ts
io-ts copied to clipboard
Add codec for when multiple attributes are required in unison
🚀 Feature request
Current Behavior
Often I come across a situation where if I have one attribute, another attribute should also be required
For example: A
by itself is ok, but if B
is defined C
also needs to be defined and vice versa
{ A: ""} // Ok
{ A: "", B: "", C: "" } // Ok
{ A: "", B: undefined, C: undefined } // Ok
{ A: "", B: undefined } // Ok
{ A: "", C: undefined } // Ok
{ A: "", B: "" } // Not Ok
{ A: "", C: "" } // Not Ok
To make this work right now I create a each type individually like the following
const myCodec = t.intersection([
t.type({ A: t.string }),
t.union([
t.type({ B: t.string, C: t.string }),
t.partial({ B: t.undefined, C: t.undefined }),
]),
])
Desired Behavior
Adding a convenience function that:
- If one property is defined, all properties match their type
- If one property is undefined, all properties are undefined or not included
- if one property is not included, all properties are undefined or not included
Suggested Solution
const myCodec = t.intersection([
t.type({ A: t.string }),
t.optionalType({ B: t.string, C: t.string })
])
Unsure of naming:
-
t.allOrNone
-
t..optional
-
t.optionalType
-
t.together
Who does this impact? Who is this for?
In my particular case this is fairly common pattern for request body validation on and api. My guess is that this is a common scenario
Describe alternatives you've considered
Writing the convenience function outside of the library
A loose untested version
const optionalType = <P extends t.Props>(
props: P
) => {
const toUndefinedEntry = (key: string) => [key, t.undefined] as const
const undefinedEntries = Object.keys(props).map(toUndefinedEntry)
const obj = Object.fromEntries(undefinedEntries) as Record<
keyof P,
t.UndefinedC
>
return t.union([t.type(props), t.partial(obj)])
}
const myCodec2 = t.intersection([
t.type({ A: t.string }),
optionalType({ B: t.string, C: t.string }),
])
Using the current solution of duplicating the pattern for each usage
const myCodec = t.intersection([
t.type({ A: t.string }),
t.union([
t.type({ B: t.string, C: t.string }),
t.partial({ B: t.undefined, C: t.undefined }),
]),
])
Additional context
I tried to make the convince type by using t.record
with t.partial
const optionalType = <P extends t.Props>(props: P) =>
t.union([
t.type(props),
t.partial(t.record(t.keyof(props), t.union))
])
However t.partial
and t.record
is not compatible, it looks like https://github.com/gcanti/io-ts/issues/429 is related this
Your environment
Software | Version(s) |
---|---|
io-ts | 2.2.16 |
fp-ts | 2.11.4 |
TypeScript | 4.4.3 |