I have this TypeScript playground, where I demonstrate how you can pass in arbitrary JSON, but the output type is not correctly typed:
// THIS `x` VARIABLE IS INCORRECTLY TYPEDconst x = convert({ input: { format: 'food', file: { path: 'x.food' } }, output: { format: 'bar' }})// THIS `out` VARIABLE IS INCORRECTLY TYPEDconst out = convert(JSON.parse(JSON.stringify({ input: { format: 'food', file: { path: 'x.food' } }, output: { format: 'bar' }})))function convert(props: ConvertA | ConvertB | ConvertC) { if (supportsA(props.input.format, props.output.format)) { return convertA(props) } if (supportsB(props.input.format, props.output.format)) { return convertB(props) } if (supportsC(props.input.format, props.output.format)) { return convertC(props) } return void}function supportsA(a: string, b: string) { return Boolean(a.match(/food?/) && b.match(/bard?/))}function supportsB(a: string, b: string) { return Boolean(a.match(/bard?/) && b.match(/bazd?/))}function supportsC(a: string, b: string) { return Boolean(a.match(/bazd?/) && b.match(/food?/))}function convertA(props: ConvertA): OutputA { console.log(props) return { file: { path: 'hello-world.png' } }}function convertB(props: ConvertB): OutputB { console.log(props) return { directory: { path: 'asdf' } }}function convertC(props: ConvertC): OutputC { console.log(props) return { file: { path: 'random.jpg' } }}type ConvertA = { input: { format: 'foo' | 'food', file: { path: string } } output: { format: 'bar' | 'bard', }}type ConvertB = { input: { format: 'bar' | 'bard', file: { path: string } } output: { format: 'baz' | 'bazd', }}type ConvertC = { input: { format: 'baz' | 'bazd', file: { path: string } } output: { format: 'foo' | 'food', }}type OutputA = { file: { path: string }}type OutputB = { directory: { path: string }}type OutputC = { file: { path: string }}
How do you allow for both:
- Arbitrary JSON as input into the function.
- AND having a typed interface to the function when manually writing the code.
And then get the output to be correctly typed? What sort of techniques do you need to use to solve this? Where does as
need to go and how do you use it here given the nested props check input.format
and output.format
?
The way I started to try and solve it was, create two functions:
convertInternal
: An untyped function input (but the nested stuff is typed in the function).convert
: A typed interface, which calls into the untyped function.
But there's a host of problems I'm encountering with that, so trying to boil it down to what needs to be done here and get it working at a more basic level.
The goal is to be able to call this function from these places:
- A REST API JSON body object (untyped).
- The CLI (untyped args serialized into an object).
- Manual code written (typed).
I use zod in a bunch of places for parsing untyped data and converting it to typed. But I'm wondering why my code above isn't working, and how to get it correctly typed. I might have zod.parse
directly inside the convertA/convertB/convertC
functions, but how do I check in the const out = convert(...)
level what the type of out
is? Do I need to do some setup before hand with the input? The out
doesn't have much information on what it is, so I'm not quite sure how to figure out the type statically.