First, you will need to change your initialization of object so that the compiler knows it's supposed to care about the literal types of the string-valued properties. Otherwise you'll get a value of type {root:{path:string},paths:{path:string}[]} and then no matter what you do you'll get just string out. So let's use a const assertion to get a more specific type for object:
const object = {
root: {
path: 'path1',
},
paths: [
{
path: 'path2',
},
{
path: 'path3',
}
],
} as const;
That gives us the following type:
/* const object: {
readonly root: { readonly path: "path1"; };
readonly paths: readonly [
{ readonly path: "path2"; },
{ readonly path: "path3"; }
];
} */
Great, the compiler knows about "path1", "path2", and "path3".
Then you will need to determine how to identify the types you care about. One possibility is to recursively descend through the type of object and build up a union of any string literal property value types you find along the way. Here's a recursive conditional type that does this:
type DeepStringLiteralValues<T> =
T extends string ? string extends T ? never : T :
T extends readonly any[] ? DeepStringLiteralValues<T[number]> :
T extends object ? { [K in keyof T]-?: DeepStringLiteralValues<T[K]> }[keyof T] : never;
For any type T, if T is itself a string literal type, then DeepStringLiteralValues<T> will just be T. Otherwise, if it's an arraylike type, we evaluate DeepStringLiteralValues<T[number]> for each element type of the array. Otherwise, if it's an objectlike type, we gather the union of DeepStringLiteralValues<T[K]> for every key K in keyof T. Otherwise, it's not a string or an array or an object, so we ignore it and return never. Let's see what that does:
type ExtractedPaths = DeepStringLiteralValues<typeof object>
// type ExtractedPaths = "path1" | "path2" | "path3"
Looks good.
It's also conceivable that your intent is to just get a union of all recursive properties with the key named "path". If so you can do this instead:
type DeepPropKey<T, K extends PropertyKey> =
K extends keyof T ? T[K] :
T extends readonly any[] ? DeepPropKey<T[number], K> :
T extends object ? { [P in keyof T]-?: DeepPropKey<T[P], K> }[keyof T] :
never;
It's similar to the other version, but DeepPropKey<T, K> is trying to find property value types for properties with key K, instead of grabbing any string literal it can find. For your example, it produces the same output, though:
type ExtractedPaths = DeepPropKey<typeof object, "path">
// type ExtractedPaths = "path1" | "path2" | "path3"
Playground link to code
objectneeds to be changed (e.g., using aconstassertion) in order for the compiler to keep track of those strings, otherwise they're all juststring. And also note thatconst extractedPaths = 'path1' | 'path2' | 'path3'is not a type but a value and a weird value at that. Presumably you meanttype ExtractedPaths = ...instead. Let me know if this addresses your question and I can maybe write up an answer; otherwise, what am I missing?