Issue
I am trying to adjust a TypeScript interface based on certain keys in an object.
I have an object coming from GraphQL that looks something like this,
{
"vamm": {
"__typename": "Vamm",
"stats": {
"__typename": "VammStats",
"fee": {
"amount": "0.01",
"__typename": "Amount"
}
}
}
}
I wrote a function which accepts these GraphQL data objects and recursively looks for a key of __typename equaling Amount. Upon finding a match, that object will be replaced with a class.
interface QueryObject {
__typename: string
[key: string]: unknown
}
type FormattedQueryData<Data> = unknown
const formatQueryData = <Data>(data: Data): FormattedQueryData<Data> => {
if (typeof data === "object") {
if ("__typename" in data) {
const queryObject = data as unknown as QueryObject
if (queryObject.__typename === "Amount") {
const queryAmount = queryObject as QueryAmount
return new Amount(queryAmount.amount)
}
}
const accumulator: Record<string, unknown> = {}
for (const key in data) {
accumulator[key] = formatQueryData(data[key])
}
return accumulator
}
return data
}
This function will return an object with an identical shape except for the value of fee, which will no longer equal,
{
"fee": {
"amount": "0.01",
"__typename": "Amount"
}
}
but instead it will equal the below value.
{
"fee": Amount // class
}
Is there a way to update the FormattedQueryData type to reflect the returned object?
Here is a TypeScript playground.
Solution
My approach here would be to make FormattedQueryData<T> a recursive conditional type that converts {amount: string, __typename: string} to Amount, and otherwise maps each object property via FormattedQueryData<T>. Like this:
type FormattedQueryData<T> = T extends { amount: string, __typename: string } ?
Amount : { [K in keyof T]: FormattedQueryData<T[K]> };
Note that mapped of the form {[K in keyof T]: ...} don't change primitive T types, so if T is string you just get string.
Let's see that it does what you want to Input:
type FormattedInput = FormattedQueryData<Input>;
/* type FormattedInput = {
vamm: {
__typename: string;
stats: {
__typename: string;
fee: Amount;
};
};
} */
That's the same as your ExpectedOutput type. So we can write this without error
const formattedData: ExpectedOutput = formatQueryData<Input>(graphqlData); // okay
And in face that type specification of Input is unnecessary, since it gets inferred:
const formattedData: ExpectedOutput = formatQueryData(graphqlData); // okay
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.