Stuck with a TS error for `Object.keys` 😞

Hello all, got a pretty nasty issue that I need to fix otherwise I will not be able to move on with my application. The issue is as follows. I am getting some data from an API, I build the call with an array of strings, and the API response is formatted as an object where every key contains an object with some other data. Let's say for example:
const fruitsIds = ['apple', 'orange', 'kiwi']

const fruitsAPIResponse = {
apple: {
taste: 'good',
price: 3,
},
orange: {
taste: 'fresh',
price: 5,
},
kiwi: {
taste: 'meh', // with all respects to whom likes them
price: 2,
},
};
const fruitsIds = ['apple', 'orange', 'kiwi']

const fruitsAPIResponse = {
apple: {
taste: 'good',
price: 3,
},
orange: {
taste: 'fresh',
price: 5,
},
kiwi: {
taste: 'meh', // with all respects to whom likes them
price: 2,
},
};
And now I want to store this information inside my Prisma database looping at each object:
const prices = Object.keys(data).map((key) => {
return {
name: key,
price: data[key].price,
};
});
const prices = Object.keys(data).map((key) => {
return {
name: key,
price: data[key].price,
};
});
The issue is that as soon as I type .price I get Object is possibly: undefined with many other errors. Googled a bit and I found a (great video of Matt Pocock)[https://youtu.be/GW00zebIt0g] that explain exactly this issue and helped me create the following function:
const objectKeys = <Obj extends FruitPricesResponse[]>(
obj: Obj
): (keyof Obj)[] => {
return Object.keys(obj) as (keyof Obj)[];
};
const objectKeys = <Obj extends FruitPricesResponse[]>(
obj: Obj
): (keyof Obj)[] => {
return Object.keys(obj) as (keyof Obj)[];
};
I've solved many of the issue I faced but not the last one and it makes me impossible to massage the data and prepare it to be stored in the database. Those are my types:
type FruitPricesResponse = {
[key: string]: {
taste: string,
price: number,
},
};
type FruitPricesResponse = {
[key: string]: {
taste: string,
price: number,
},
};
I really don't know how to move forward from here, hope someone will be able to help me out.
Matt Pocock
YouTube
Improving OBJECT.KEYS in TypeScript - Advanced TypeScript
Become a TypeScript Wizard with Matt's upcoming TypeScript Course: https://www.mattpocock.com/ Follow Matt on Twitter https://twitter.com/mattpocockuk
17 Replies
deforestor
deforestor•13mo ago
What is the last one in this case? Because I assume it can't be that it's possibly undefined, so I'm not sure what the issue is now
Sybatron
Sybatron•13mo ago
Why you make FruitPricesResponse[]? Just
const objectKeys = <Obj extends FruitPricesResponse>(
obj: Obj
): (keyof Obj)[] => {
return Object.keys(obj) as (keyof Obj)[];
};
const objectKeys = <Obj extends FruitPricesResponse>(
obj: Obj
): (keyof Obj)[] => {
return Object.keys(obj) as (keyof Obj)[];
};
otherwise you the keys will be '0','1', etc as you pass it array of responses not a response
cupofcrypto
cupofcrypto•13mo ago
you are right there, mistakenly I passed it as an array instead it was just one object 😅 Unfortunately, the undefined thing persists 😞 Even if I write something like this:
const prices = objectKeys.keys(data).map((key) => {
if (data[key] === undefined || key === undefined) return;

return {
name: key,
price: data[key].price,
};
});
const prices = objectKeys.keys(data).map((key) => {
if (data[key] === undefined || key === undefined) return;

return {
name: key,
price: data[key].price,
};
});
Sybatron
Sybatron•13mo ago
with the code like this i get that
cupofcrypto
cupofcrypto•13mo ago
I ran a console.log on the resulting array and looks fine to me:
[
{
name: 'apple'
price: 3,
},
{
name: 'orange',
price: 5,
},
{
name: 'kiwi',
price: 2,
},
]
[
{
name: 'apple'
price: 3,
},
{
name: 'orange',
price: 5,
},
{
name: 'kiwi',
price: 2,
},
]
Sybatron
Sybatron•13mo ago
are you validating that there is something in the response, if not probably that's where it comes from
cupofcrypto
cupofcrypto•13mo ago
you mean with a silly if(!data) return;? Because as I shown you before I am trying to exit if data[key] is undefined but it ignores it. I even tried to filter out all the undefined but now the only thing that seems to make it happy is to use the optional chaining 😮 Like now is happy like this:
const prices = objectKeys.keys(data).map((key) => {
return {
name: key,
price: data[key]?.price,
};
});
const prices = objectKeys.keys(data).map((key) => {
return {
name: key,
price: data[key]?.price,
};
});
I'll filter out later the items that will not have a price 😅 really confusing for me this part, but is the kind of experience that let you learn something new right?
epsilon42
epsilon42•13mo ago
Because as I shown you before I am trying to exit if data[key] is undefined
You can't really exit out of a .map it will just return an undefined for that spot in the resulting array.
cupofcrypto
cupofcrypto•13mo ago
actually you're totally right here, got lost and confused by the pressure to solve it 😂 Thanks for the reminder
epsilon42
epsilon42•13mo ago
so is there still a problem youre having
cupofcrypto
cupofcrypto•13mo ago
it's gonna be ugly as hell but probably I could fix it just like that: data[key]?.price?.toString() ?? '0', Because I'll need a string 😅 so I'll take the 0 in case of undefined and I'll store it like that and keep it like that for the time being. I'll have to understand why this is still giving me the same undefined error 🤔
const prices = objectKeys(data)
.filter((key) => (data[key] !== undefined && data[key]?.price !== undefined))
.map((key) => {
return {
coingecko_id: key,
latestPrice: data[key].price.toString(),
};
});
const prices = objectKeys(data)
.filter((key) => (data[key] !== undefined && data[key]?.price !== undefined))
.map((key) => {
return {
coingecko_id: key,
latestPrice: data[key].price.toString(),
};
});
well I got my head busy with my true work for now, I'll get back to this as soon as I can
epsilon42
epsilon42•13mo ago
its a bit hard to tell, but i think it's probably something to do with the response. when you hover over the api response what type does it give you? doesn't seem right that you need to be doing this undefined check.
cupofcrypto
cupofcrypto•13mo ago
well probably the issue is mine then because I had to do something like this in order to assign the value to the response:
const response = await fetch(url); // External URL
const data = (await response.json()) as FruitPricesResponse;
const response = await fetch(url); // External URL
const data = (await response.json()) as FruitPricesResponse;
And if I omit that gives me the any error
Sybatron
Sybatron•13mo ago
probably something like zod schema will be good to validate that what you want is what you get and not just undefined, cuz this fetch may not return what you really want and can be undefined
cupofcrypto
cupofcrypto•13mo ago
the thing is that I have no control on that URL, I am fetching data from outside my app. But just to understand, by using zod schema to validate you mean something like this: https://www.brenelz.com/posts/using-zod-to-validate-api-responses I am still trying to figure out some part of the tools T3 offer us 😅 So I'll parse the response and if Zod throws an error will not get to that part of the code... I'll test it tomorrow but seems to be the right way to go, let me know if I got it and thanks for the help
epsilon42
epsilon42•13mo ago
Using fetch with TypeScript
How to make HTTP requests with fetch and TypeScript
cupofcrypto
cupofcrypto•13mo ago
Still need to go through the article you suggested me but I found a working solution:
const FruitPricesSchema = z.object({
taste: z.string(),
price: z.number().nonnegative(),
});

const FruitPricesResponseSchema = z.record(
z.string().min(1),
FruitPricesSchema
);

type FruitPricesResponse = z.infer<typeof FruitPricesResponseSchema>;

const response = await fetch(url);
const data: FruitPricesResponse = FruitPricesResponseSchema.parse(
await response.json()
);

const objectKeys = <Obj extends FruitPricesResponse>(
obj: Obj
): (keyof Obj)[] => {
return Object.keys(obj) as (keyof Obj)[];
};

const prices = objectKeys(data).map((key) => {
return {
name: key,
price: data[key]?.price.toString() ?? '0',
};
});
const FruitPricesSchema = z.object({
taste: z.string(),
price: z.number().nonnegative(),
});

const FruitPricesResponseSchema = z.record(
z.string().min(1),
FruitPricesSchema
);

type FruitPricesResponse = z.infer<typeof FruitPricesResponseSchema>;

const response = await fetch(url);
const data: FruitPricesResponse = FruitPricesResponseSchema.parse(
await response.json()
);

const objectKeys = <Obj extends FruitPricesResponse>(
obj: Obj
): (keyof Obj)[] => {
return Object.keys(obj) as (keyof Obj)[];
};

const prices = objectKeys(data).map((key) => {
return {
name: key,
price: data[key]?.price.toString() ?? '0',
};
});
Unfortunately, even if I parse the response I get that nasty undefined was still popping up preventing me from writing to the database. I am sure I am doing something wrong, and reading that article will help me in figuring out what it is, but at least now I am able to have something defined to store into the database 😊 Opened this as a issue on my repo and linked this profitable discussion, as soon as I have time to fix this I'll get back to it. Right now I am all in feature implementations 😂