Reputation: 1295
I'm trying to write a tool to generate API clients with typesafe calls, adding libs that will take care of validating the input and sorting it out.
I would like to implement a global transforming option to allow users to modify the responses based on the type given.
Suppose we have a set of types that all share a Base i.e.
type Base<NAME extends string, T> = {
name: NAME;
value: T;
}
// All the possible responses given to the API
type ALL = Base<'Animal', Animal> | Base<'ZooKeeper', ZooKeeper> | Base<'Visitor', Visitor>;
And I want to write a function to transform all the Animal
to TaggedAnimal
and ZooKeeper
to Keeper
i.e.
const transformer = (value: ALL) => {
if (value.name === 'Animal') {
return {
name: 'TaggedAnimal',
value: {
...value.value,
tag: 'mytag' // or something else made up of animal attributes
}
} as Base<'TaggedAnimal', TaggedAnimal>;
} else if (value.name === 'ZooKeeper') {
return {
name: 'Keeper',
value: {
id: value.value.id
}
} as Base<'Keeper', Keeper>;
}
return value;
}
So far so good, but the problem lies when I try to use this function on a specic API.
const getAnimal = (): Base<'Animal', Animal> => {
// do network request, validation, etc
return {
name: 'Animal',
value: {
id: '123',
name: 'Lion'
}
} as Base<'Animal', Animal>;
}
const animal = getAnimal(); // Good! type of animal: Base<'Animal', Animal>
const tanimal = transformer(animal); // :/! type of tanimal: Base<'TaggedAnimal', TaggedAnimal> | Base<'Keeper', Keeper> | Base<'Visitor', Visitor>;
I understand that this happens because the transformer
expects all the types and returns a fixed subset (given by the function).
Is there any way to go about this with the current version of typescript (4.7)?
I have tried using generics to narrow down i.e.:
const transformer = <IN extends ALL>(value: IN) => {
// ...
}
const tanimal = transformer(animal); // :/! type of tanimal: Base<'Animal', Animal> | Base<'TaggedAnimal', TaggedAnimal> | Base<'Keeper', Keeper>;
Upvotes: 1
Views: 35
Reputation: 33071
You need to overload your function:
interface Animal {
id: string;
name: string;
}
interface ZooKeeper {
id: string;
shift: string;
}
interface Visitor {
id: string;
fee: number;
}
interface TaggedAnimal extends Animal {
tag: string;
}
interface Keeper {
id: string;
}
type Base<NAME extends string, T> = {
name: NAME;
value: T;
}
// All the possible responses given to the API
type ALL = Base<'Animal', Animal> | Base<'ZooKeeper', ZooKeeper> | Base<'Visitor', Visitor>;
function transformer(value: Base<'Visitor', Visitor>): Base<'Visitor', Visitor>
function transformer(value: Base<'ZooKeeper', ZooKeeper>): Base<'Keeper', Keeper>;
function transformer(value: Base<'Animal', Animal>): Base<'TaggedAnimal', TaggedAnimal>
function transformer(value: ALL) {
if (value.name === 'Animal') {
return {
name: 'TaggedAnimal',
value: {
...value.value,
tag: 'mytag' // or something else made up of animal attributes
}
}
} else if (value.name === 'ZooKeeper') {
return {
name: 'Keeper',
value: {
id: value.value.id
}
}
}
return value;
}
const getAnimal = (): Base<'Animal', Animal> => ({
name: 'Animal',
value: {
id: '123',
name: 'Lion'
}
})
const animal = getAnimal(); // Good! type of animal: Base<'Animal', Animal>
const tanimal = transformer(animal); // Base<"TaggedAnimal", TaggedAnimal>
Upvotes: 2