#Accessing dynamic properties

48 messages · Page 1 of 1 (latest)

full mountain
#

I'm working with a user object (fetched from a GetMember endpoint) defined roughly as follows:

interface Member {
  id: string;
  name: string;
  avatarUrl: string;
  color: string;
  info: Record<string, string>;
}

The info record contains user-defined fields for display on their profile. A specific user's field definitions can be fetched from a GetFields/{userId} endpoint:

interface MemberField {
  id: string;
  name: string;
  type: number;
}

The Member.info record will contain every field returned from this response, so as the developer I know the properties exist, but I can't figure out how to tell Typescript that. Is this something that would be better handled by a validator?

jade steppe
#

so as the developer I know the properties exist
will it always return the same set of properties? or are the properties different for every user?

#

@full mountain

full mountain
#

The properties are different for every user but I'm accessing them based on the GetFields response for that user

jade steppe
#

right, so you can't know what keys you will have

full mountain
#

Right

jade steppe
#

so you basically have no info other than that board Record<string, string> type

#

so you simply want to validate that what you recieve is an obejct where the keys are strings, and the values are also strings?

#

you can validate that at runtime, but it won't give you any more info

full mountain
#

I'm good on validating what I receive, I just can't then access the properties on the user without non-null assertions or the like. It sounds like that might be what I have to do though, or maybe Zod/other validator

#

Basically "I know this user has property A, can I have user.info.A?"

jade steppe
#

do you want to access a specific property? or all the available properties, whetever they might be?

#

alright

#

well, you still need to check it
and through checking for it, TypeScript will know it exists

#

it's called narrowing

#

in JS, you can use the in operator for that

gritty sealBOT
#
declare const c: Record<string, string>;

if("foo" in c) {
    c.foo;
//     ^? - string
}
jade steppe
#

it also works with Object.hasOwnProperty or Object.hasOwn iirc

full mountain
#

Hmm, yeah, I think my issue was that I needed to do that everywhere I'm accessing the property and it just didn't feel clean. I'd rather repeat myself a little than disable Typescript features though

#

Thank you, I'll give that a shot when I'm back on my personal project and report back 🙂

jade steppe
#

but if you know that property will always be present, why don't you specify it in the type in the first place?

full mountain
#

The key in the record is the id from the GetFields response which is different between every user

#

Trying to think of how to phrase it

#

I don't know at compile time what the actual properties are, but in the code I use the GetFields response to know what properties on the user I need to access

jade steppe
#

so in info: Record<string, string>;,
the values are keys that can be used to call an endpoint

and on that endpoint, you have the full object
that that object is always of type UserField?

full mountain
#

Just realized I incorrectly communicated the UserField type, there is no value there. The value is on the User.info keyed off of the UserField.id

#

Phrasing things poorly, sorry 😭

#

Edited to clarify a little. The fields are per-user, the user will have multiple Members on their account that each have all of the user-defined fields

jade steppe
#

I'm not sure I understand your problem then

#

you have info: Record<string, string>; where you say you don't know the keys in advance

#

then later you say that you are trying to access specific keys

#

is that correct?

full mountain
#

Yes, but the specific keys are pulled from the GetFields response which I don't have at runtime as it's user-specific

jade steppe
#

you want to fire requests to fetch all the members, then merge them into an object
and access the values

full mountain
#

Let me type up some example responses, that might help

#

Thanks for bearing with me on this

#

GetMember

{
  id: "janedoe1",
  name: "Jane Doe",
  info: {
    "abc123": "Baseball"
  }
}

GetFields

[{
  id: "abc123",
  name: "Favorite sport",
  type: 1
}]

So I only know that User.info.abc123 exists after making the GetFields call which is why User.info is currently a Record<string, string>

#

Thankfully I do know that the keys and values are always both strings

jade steppe
#

So I only know that User.info.abc123 exists after making the GetFields call
are you sure about that?
if /getMember returns info: { "abc123": "Baseball" }, then we know User.info.abc123 exists, right?

#

but anyway

#

what keys are you trying to access on the info object?
the one defined in the object? or pre-defined keys?

full mountain
#

That's a good point. So I guess what I'm looking for is a way to, upon the GetMember call, populate the type of info with the keys that are there

#

I don't know at compile time though, because it depends on the logged in user

jade steppe
#

right, it's not something you can do at compile-time, only at runtime

full mountain
#

Okay, so validation or narrowing would be the best approach. Thanks for your help and patience 🙂