This is called correlated record types, and has quite a discussion in the TypeScript community. My approach is to move the correlated record check to runtime:
https://github.com/microsoft/TypeScript/issues/30581#issuecomment-686621101
https://repl.it/@urgent/runtime-fp-ot
Step 1
Create a general Prop
interface holding all allowable properties.
interface Prop {
label: "user" | "post",
userid: string,
body: string
}
This does prevent type specific properties, and you could have collisions. user.id
wants to be a number
but post.id
wants to be a string
. Not a big deal to me. Think of a different property name specific to your type, accept what's there, or if you are adventurous try adding a dimension to Props
and indexing by type.
Step 2
Create a map of labels to runtime decoders. In typescript I use a class so I can extend it through different files:
class Decoder {
user: userDecode,
post: postDecode
}
Step 3
Create a function which takes props, looks up decoder from class prototype, and performs runtime decode
(props:Props) => Decoder.prototype[props.label].decode(props as any)
io-ts needs to widen here for any
in strict mode. Also you need a check that props.label
exists in Decoder
Step 4
Create similar maps of functions to run. If you call them after the runtime decode, you know runtime is passing valid values.
Cons
- Much more complex than writing out and managing switch statements. TypeScript type narrows for
switch
automatically.
- Property collisions. No specific properties to the type without added work.
- Need to manually check type exists.
switch
will ignore, and use a default case
Pros
- You can close the runtime processing for good. Isolated, no opening that code if you need to add support for different runtime types.
- When creating support for different runtime types, like
page
or file
, something like babel file can import new files automatically.
- Good for version control and developer access permissions. New types can be opened up for public submission. Core processing can remain closed. So you have the start of a DSL.