#Convert leaf nodes of a ragged object to their paths
73 messages · Page 1 of 1 (latest)
!:autopath
Preview:```ts
...
type User = {
id: string,
name: string,
age: number,
friends: User[],
parents: [User, User],
}
get(user, "friends.0") // returns string
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```
plus some minor modifications to exclude objects
Wow, complicated.
Conceptually, I was thinking of this, but I don't know how to do a "mapped array" that doesn't produce an object...
type Combine<TLeft extends string, TRight extends string> = TLeft extends '' ? TRight : `${TLeft}.${TRight}`
type KeyPaths<T, TBase extends string = ''> = T[P in keyof T] extends string
? Combine<TBase, P>
: KeyPaths<T[P], Combine<TBase, P>>
I mean, obviously the above is incorrect.
!hb discrim
keyof T extends infer P ? P extends infer P ? use P here : never : never
uhhh
or rather
I just didn't know how to logically do the inference P of each item in the union
!ts
const x = {
outer: {
second: {
one: 'a',
two: 'b'
}
},
another: {
three: 'c'
},
four: 'd'
};
type Combine<TLeft extends string, TRight extends string> = TLeft extends '' ? TRight : `${TLeft}.${TRight}`
type KeyPaths<T, TBase extends string = ''> = keyof T extends infer P extends keyof T & string ? P extends P ? T[P] extends string
? Combine<TBase, P>
: KeyPaths<T[P], Combine<TBase, P>> : never : never;
type What = KeyPaths<typeof x>;
// ^? - type What = "four" | "outer.second.one" | "outer.second.two" | "another.three"```
@tired quiver
Preview:```ts
const x = {
outer: {
second: {
one: "a",
two: "b",
},
},
another: {
three: "c",
},
four: "d",
}
type _KeyPaths<
T,
Prefix extends string = ""
= `${Prefix}.${{
[K in keyof T]: K extends string
? T[K] extends object
? _KeyPaths<T[K], K>
: K
: neve
...```
My stab
Is there a way to make a TS Playground line show the error message at a location, similar to \\ ^? but to show an error message instead of the type at that location?
No
typescript iterates through unions instead of passing the entire union, when the left side of an extends is a bare type parameter
That is clever, and I was thinking about how an object could easily be turned into its values using { }[key of T] like you did.
Thank you both!
The complexity of TypeScript is kind of cool, even though sometimes I get stuck, but I know I can master it eventually...
woops
Preview:ts ... type KeyPaths<T, Prefix extends string = never> = `${[ Prefix ] extends [never] ? "" : `${Prefix}.`}${{ [K in keyof T & string]: T[K] extends object ? KeyPaths<T[K], K> : K }[keyof T & string]}` ...
Fair
Last time I tried = never, I distributed over it so the entire thing became never
See, little tricks like [K in keyof T & string] are cool. 🙂
it's [never], not never, which changes how it distributes
... right, n_n? 😄
wops x 2
Preview:ts ... type KeyPaths<T> = { [K in keyof T & string]: T[K] extends object ? `${K}.${KeyPaths<T[K]>}` : K }[keyof T & string] ...
yup - since the outer type is no longer a union
@trim crescent no never :D
Yep that looks so much neater
I'm creating some config and want to be able (for now) to require one part of the config to only mention paths from another part of the config.
Mad people stuck with recursion
actually it's not even really prolog recursion
That would require the createConfig<T>(c: T): T { return c } trick
The codebase I inherited for this project has secrets committed to code, which is horrible, but I can't fix it all at once. So the first step is to put all the secrets into their own object, and refer to them in the regular config. Then I can migrate the secrets into proper secret storage.
For now it's all just literals, so I can just do const secrets = {} (my x object above) and then in the const config = { } I can type some keys as secret-name references
And if a secret is compound, I can figure out how to jimmy the paths computation to include the legitimate secrets that are objects, e.g. { username: string, password: string } or something like that.
!resolved
Thank you both!
... definitely loving the type intersection trick to filter the keys of T into their string values only
is this a valid way to ensure type safety without a forced typecast that as would do? secretName: 'path.to.secret' satisfies SecretPath?
yup, satisfied is fully type-safe
yeah, Extract does something similar... but i think it's not really required for things that & would suffice for
i think Extract works better to make unresolved generics satisfy constraints?
but honestly the way to go is probably this - "use Extract<T, Constraint> when T & Constraint doesn't work"
@cerulean stone Is there an easy way to get your last version of KeyPaths to stop at string types and not go into the methods on string such as anchor, [Symbol.iterator], at, big, blink, bold, charAt, charCodeAt, ...?
It's creating very many more types in the union than I expected.
My attempt failed
type KeyPaths<T> =
T extends string
? T
: { ... }
But string extends object is false, so why is it traversing into String prototype properties?
Also, I upgraded my IDE (WebStorm/IntelliJ) to the latest version and now KeyPaths is not recursing (in IDE intellisense) into the children.
using the playground link in the embed the bot sent, i see this
Hmmmmmmm you're right. Lemme think about that.
did you change KeyPaths or the input?
I changed the input, yes, but not KeyPaths. Though I won't be able to go back to the situation where those extra string methods were included, because after upgrading my IDE it has a new problem... sigh.
Okay, weird, it is working on the original object in my IDE.