0

Using the Decoder API, is there a way to define a decoder with mutually exclusive properties?

import * as D from 'io-ts/Decoder';

const decoder = pipe(
  D.struct({
    a: D.string
  }),
  D.intersect(
    D.partial({
      b: D.string,
      c: D.boolean
    })
  )
);

The above sets up a situation where b and c can both be present, but are optional. How could I instead require that one of b or c must be present, but not both?

Rich Churcher
  • 7,361
  • 3
  • 37
  • 60

1 Answers1

1

You can use the Union combinator.

const decoder = pipe(
  D.struct({
    a: D.string
  }),
  D.intersect(
    D.union(
      D.struct({ b: D.string }),
      D.struct({ c: D.string })
    )
  )
);

Bear in mind that you won't be able to access b or c without first checking if those properties exist, since Typescript doesn't have a way to know which one of the two is present in your object.

type Decoder = D.TypeOf<typeof decoder>
declare const myDecoder: Decoder;

myDecoder.a // inferred as `string`
myDecoder.b // TYPE ERROR: Property 'b' does not exist on type '{ a: string; } & { c: string; }'
myDecoder.c // TYPE ERROR: Property 'c' does not exist on type '{ a: string; } & { b: string; }'

if ("b" in myDecoder) {
  myDecoder.b // inferred as `string`
}

if ("c" in myDecoder) {
  myDecoder.c // inferred as `string`
}

Notice how, when checking for both the mutually exclusive properties, you get a type error. TypeScript correctly infers that it's a case that can never happen (myDecoder is inferred as never inside the if block)

if ("b" in myDecoder && "c" in myDecoder) {
  myDecoder.b // TYPE ERROR: Property 'b' does not exist on type 'never'.
  myDecoder.c // TYPE ERROR: Property 'c' does not exist on type 'never'.
}
Alerosa
  • 568
  • 1
  • 5
  • 14