io-ts
io-ts copied to clipboard
Better validation errors for tagged unions
🚀 Feature request
Current Behavior
const C = t.union([
t.type({
type: t.literal('A'),
foo: t.number,
}),
t.type({
type: t.literal('B'),
bar: t.number,
}),
]);
// Validation errors have errors for A too
// e.g.
// 0.type expected 'A', actual 'B'
// 0.foo expected number, actual undefined
// 1.bar expected number, actual 'wrong type'
C.decode({ type: 'B', bar: 'wrong type' }).value
Desired Behavior
It would be great if there was a way to reduce validation errors if there's a tag match. I have a tagged union of 10 codecs with dozens of properties and it is very hard to understand what is actually wrong. So with above example codec, I'd expect the following validation error:
1.bar expected number, actual 'wrong type'
I can't repro with [email protected] or [email protected]
import * as t from 'io-ts'
import { PathReporter } from 'io-ts/lib/PathReporter'
const C = t.union([
t.type({
type: t.literal('A'),
foo: t.number
}),
t.type({
type: t.literal('B'),
bar: t.number
})
])
console.log(PathReporter.report(C.decode({ type: 'B', bar: 'wrong type' })))
// [ 'Invalid value "wrong type" supplied to : ({ type: "A", foo: number } | { type: "B", bar: number })/1: { type: "B", bar: number }/bar: number' ]
Oh I see, my bad.
I've checked the io-ts implementation now, union tag detection needs a TypeC or a StrictC and I was using some custom types.
Alright, I'll reopen this so that this can be thought about. The only difference is mapOutput.
import * as t from 'io-ts'
import { mapOutput } from 'io-ts-types'
import { PathReporter } from 'io-ts/lib/PathReporter'
const C = t.union([
mapOutput(t.type({
type: t.literal('A'),
foo: t.number
}), x => x),
t.type({
type: t.literal('B'),
bar: t.number
})
])
console.log(PathReporter.report(C.decode({ type: 'B', bar: 'wrong type' })))
// 0: "Invalid value "B" supplied to : ({ type: "A", foo: number } | { type: "B", bar: number })/0: { type: "A", foo: number }/type: "A""
// 1: "Invalid value undefined supplied to : ({ type: "A", foo: number } | { type: "B", bar: number })/0: { type: "A", foo: number }/foo: number"
// 2: "Invalid value "wrong type" supplied to : ({ type: "A", foo: number } | { type: "B", bar: number })/1: { type: "B", bar: number }/bar: number"
In case someone would find it useful, I implemented a more aggressive union reduction reporter:
https://github.com/klesun/io-ts-better-union-error-reporter
The built-in PathReporter only seems to successfully eliminate unions on very basic types, but leaves the excessive options if there are sophisticated intersection types involved. The lib tries to solve that.