#Extract all object properties paths as string arrays

128 messages · Page 1 of 1 (latest)

graceful thicket
#
crisp narwhalBOT
#

@graceful thicket Here's a shortened URL of your playground link! You can remove the full link from your message.

autumnlight#0

Preview:ts const paths = { home: {}, settings: { users: {}, rollouts: {}, workflows: {create: {}}, checklists: {create: {}}, "custom-fields": {}, "client-links": {}, integrations: {}, }, } as const type RawPaths = typeof paths ...

junior star
#

Do you want the result as a union?

graceful thicket
#

yeah

graceful thicket
#

its a parameter for a function, so i know which route the user tries to invoke

crisp narwhalBOT
#
const paths = {
  home: {},
  settings: {
    users: {},
    rollouts: {},
    workflows: { create: {} },
    checklists: { create: {} },
    'custom-fields': {},
    'client-links': {},
    integrations: {},
  },
} as const;
type RawPaths = typeof paths;

type ExtractPaths<O extends Record<string, unknown>> = keyof O extends infer K extends string ?
  K extends infer K extends string
    ? [K]
    : never
  : never

type Paths = ExtractPaths<typeof paths>
//   ^? - type Paths = ["home"] | ["settings"]
junior star
#

that's the first level, right?

#

the extends stuff is just to distribute the keyof union

#

then you just need to replace [K] with a O[K] extends Record<string, unknown> and use recursion to build out the full tuple, I think

#
type ExtractPaths<O extends Record<string, unknown>> = keyof O extends infer K extends string ?
  K extends infer K extends string
    ? O[K] extends Record<string, unknown>
      ? [K, ...ExtractPaths<>]
      : [K]
    : never
  : never

the spread operator there lets you combine the sub path with the first part to make a complete tuple

#

weird, it breaks for some reason 😄

graceful thicket
junior star
#

Yeah it's odd

graceful thicket
#

why do you even do :never?

#

I think it's simpelr to do it with two generics

#

Ohh, I see what you're doing

junior star
#

Maybe it's because eventually you get to the leaf node and then the recursion fails?

graceful thicket
#

i removed all nevers and it still returns never

#

i replaced the nested one with K and outer one with []

junior star
#
type ExtractPaths<O extends Record<string, unknown>> = keyof O extends infer K extends string ?
  K extends infer K extends string
    ? O[K] extends Record<string, unknown>
      ? [K, ExtractPaths<O[K]>]
      : [K]
    : never
  : never
#

try this

#

it nearly works

graceful thicket
#

im also trying around

junior star
#
type ExtractPaths<O extends Record<string, unknown>> = keyof O extends infer K extends string ?
  K extends infer K extends string
    ? O[K] extends Record<string, unknown>
      ? [K, ExtractPaths<O[K]>]
      : [K]
    : 'blah'
  : 'blah'


type T = ExtractPaths<{}>

T here is never, so I think addressing that is the answer

#

If that can be [] I think it works?

graceful thicket
#

yeah, I dont get from where we get never in the first place

junior star
#

or am I being dumb because we're returning a union not a tuple so we can't spread it?

#

instead of [K, ...ExtractPaths<O[K]>] we need to be distributing over ExtractPaths<O[K]> so we return a union of all the possibilities

graceful thicket
#

im not this good in ts to talk with you, i can be a rubber duck at most lol

junior star
#

keyof {} is never, so that's probably where the never comes from

junior star
#

So when it distributes never, it result is never

#

So this means we need to use ExtractPaths<O[K]>, check for never, if so return [K] otherwise distribute over the result, and then thinking about whether it works for 3 levels

graceful thicket
#

got it

#

i think

#
type ExtractPaths2<
  path extends string[],
  Obj extends Record<string, {}>,
> = Obj extends {[key:string]:never} ? path : ExtractPaths2<[...path, `${Extract<keyof Obj, string>}`], Obj[Extract<keyof Obj, string>]>;

junior star
#

does it work?

graceful thicket
#

seems to

#

lemme check

#

yes

#

it does

graceful thicket
junior star
#

simple is good

graceful thicket
#

im further optimizing rn

junior star
#

What types are you passing to your function?

crisp narwhalBOT
#
autumnlight#0

Preview:ts const paths = { home: {}, settings: { users: {}, rollouts: {}, workflows: {create: {}}, checklists: {create: {}}, "custom-fields": {}, "client-links": {}, integrations: {}, }, } as const type RawPaths = typeof paths ...

#
sandiford#0

Preview:ts const paths = { home: {}, settings: { users: {}, rollouts: {}, workflows: {create: {}}, checklists: {create: {}}, "custom-fields": {}, "client-links": {}, integrations: {}, }, } as const type RawPaths = typeof paths ...

junior star
#

I don't think that is correct

graceful thicket
#

aww dawn

#

sec

graceful thicket
#

basically i need to extract the key on the left and use it on the right

#

once i did that it will work

#

but i dont know how

junior star
#

?

graceful thicket
#

@junior star

#

basically if i can get these two to be equal it will work

#

thats where im stuck

junior star
#

they they are both unions right?

#

so you need to distribute

#

!hb dist

crisp narwhalBOT
graceful thicket
#

yeah, basically before I get intro extract path i need to iterate over te keys

#

OH

#

wait

junior star
#

You cand do this to iterate keyof Obj extends K extends string ? K extends infer K extends string ? ... : never : never

#

and you will get a union of the results

crisp narwhalBOT
#
const paths = {
  foo: 'b',
  home: {},
  settings: {
    users: {},
    rollouts: {},
    workflows: { create: {} },
    checklists: { create: {} },
    'custom-fields': {},
    'client-links': {},
    integrations: {},
  },
} as const;
type RawPaths = typeof paths;

type ExtractPaths<O extends Record<string, unknown>> = keyof O extends infer K extends string
  ? K extends infer K extends string
    ? O[K] extends Record<string, unknown>
      ? ExtractPaths<O[K]> extends infer R extends readonly string[]
        ? [R] extends never[]
          ? [K]
          : [K, ...R]
        : 'n/a'
      : [K]
    : 'n/a'
  : 'n/a'

type Paths = ExtractPaths<typeof paths>
//   ^? - type Paths = ["foo"] | ["home"] | ["settings", "users"] | ["settings", "rollouts"] | ["settings", "workflows", "create"] | ["settings", "checklists", "create"] | ["settings", "custom-fields"] | [...] | [...]
junior star
#

I was really struggling because if you do never extends never ? : you get no result, because distributing never is distributing into 0 iterations

#

lol

#

but [never] extends [never] is ok

graceful thicket
#

ahh

#

but generally

#

a question

#

How can I here pull the Key into a variable or something and then use it in both extractPaths argument?

#

there has to be a way

#

but I dont know it

junior star
#

I just told you, distribution

#

You don't want T = 'a' | 'b' | 'c' and G<T, T> right because it gives you a big messy union?

#

if you do G<T> = T extends string ? T : never, then its going to iterate over T and return each result into a union

#

In that case you break apart the union and then recombine it, so it does nothing much

crisp narwhalBOT
#
type G1<T extends string> = (T|null)[]
type G2<T extends string> = T extends string ? (T|null)[] : never

type R1 = G1<'foo' | 'bar'>
//   ^? - type R1 = ("foo" | "bar" | null)[]
type R2 = G2<'foo' | 'bar'>
//   ^? - type R2 = ("foo" | null)[] | ("bar" | null)[]
graceful thicket
junior star
#

compare the difference

graceful thicket
#

and trying to get that to work

junior star
#

And I'm replying to that

crisp narwhalBOT
#

@graceful thicket Here's a shortened URL of your playground link! You can remove the full link from your message.

autumnlight#0

Preview:ts const paths = { home: {}, settings: { users: {}, rollouts: {}, workflows: {create: {}}, checklists: {create: {}}, "custom-fields": {}, "client-links": {}, integrations: {}, }, } as const type RawPaths = typeof paths ...

graceful thicket
#

i am getting nthe first level

#

but its not going further

#

Oh

#

wait

junior star
#

your approach is confusing me haha

graceful thicket
#

different mindset i assume

junior star
#

So do you notice that type Paths = ["home" | "settings"] is incorrect?

graceful thicket
#

oh wait

#

i did not notice

junior star
#

I mean technically it is correct in a simple case, but you really want ["home"] | ["settings"]

#

because once you add the 2nd level, they need to be separate

graceful thicket
#

I am a bit confused on why its not an union

#

im too tired

#

i should go to bed

#

thank you for your help

crisp narwhalBOT
#

@junior star Here's a shortened URL of your playground link! You can remove the full link from your message.

sandiford#0

Preview:```ts
const paths = {
home: true,
setings: true,
} as const
type RawPaths = typeof paths

type ExtractPaths<Obj extends {}> = [keyof Obj]

// Challenge: make T into ["home"] | ["settings"]

type T = ExtractPaths<RawPaths>
// ^?```

junior star
#

Also I solved the problem a while ago if you didn't see, using my approach

graceful thicket
#

i did see that, it was just that I wanted to figure out why mine failed

#

lemme try this challenge

#

type ExtractPaths<Obj extends {}> = [keyof Obj]
I would assume that it returns two arrays, I am confused on why its a union

junior star
#

keyof Obj is a union like 'a' | 'b'

#

so this just says ['a' | 'b']

hardy jacinth
#

!:autopath

crisp narwhalBOT
#
tjjfvi#0

Preview:```ts
...
type User = {
id: string,
name: string,
age: number,
friends: User[],
parents: [User, User],
}

get(user, "friends.0") // returns User
get(user, "friends.0.age") // returns number
get(user, "this.does.not.exist") // error
get(user, "parents.3") // error, out of bounds

getString(user, "friends.0.id") // returns string
getString(user, "friends.0.age") // error, not a string```

hardy jacinth
#

couldnt you just modify this

graceful thicket
junior star
crisp narwhalBOT
#

:white_check_mark: Created snippet sandiford:paths-as-union-of-tuples

junior star
hardy jacinth
junior star
#

confusingly distribution is trigger by extends clauses within generics

graceful thicket
#

im way too tired to learn this atm, ill take a look at it tmr, thank you ❤️

hardy jacinth
twilit glade
#

Obligatory: are you sure you need this? Most places where these "array of string paths" things are used, you could use a callback property access instead.

#

i.e. instead of doSomething(["foo", "bar", "baz"]) the API could be doSomething(x => x.foo.bar.baz)