It seems like you want a type function of the form UnionOfSingleKeyObjects<T> which turns an object type T into a union of single-key object types where each key in keyof T appears exactly once. Depending on your use cases, you could define such a type function like this:
type UnionOfSingleKeyObjects<T extends object> =
{ [K in keyof T]-?: { [P in K]: T[P] } }[keyof T]
And verify that it works for PersonInfo as desired:
type PersonKVPairs = UnionOfSingleKeyObjects<PersonInfo>
/* type PersonKVPairs = {
id: number;
} | {
name: string;
} | {
address: {
street: string;
country: string;
};
} */
The definition of UnionOfSingleKeyObjects is a mapped type where we iterate over each key type K in keyof T, calculate the single-key object in question for each key, and then index into it with keyof T to get a union of all single-key object types.
Instead of indexing into a mapped type, you could use distributive conditional types to get the same effect:
type UnionOfSingleKeyObjects<T extends object> =
keyof T extends infer K ? K extends keyof T ?
{ [P in K]: T[P] } : never : never
Either way works; I tend to use mapped types because they are a bit easier to explain than conditional type distributivity.
In both of those approaches, the single-key object with key K is written as {[P in K]: T[P] }. This can alternatively be written as Record<K, T[K]> using the Record<K, V> utility type, or as Pick<T, K> using the Pick<T, K> utility type. These other versions have their pros and cons, and may change how the type appears in IntelliSense quickinfo as well as whether or not optional/readonly keys stay optional/readonly in the output. If you care about preserving these modifiers and don't want to see Pick or Record in your quickinfo, you can write it like {[P in keyof Pick<T, K>]: T[P]}, like this:
type UnionOfSingleKeyObjects<T extends object> =
{ [K in keyof T]-?: { [P in keyof Pick<T, K>]: T[P] } }[keyof T]
and we can see that such modifiers are preserved:
type Example = UnionOfSingleKeyObjects<{ a?: string, readonly b: number }>
/* type Example = {
a?: string;
} | {
readonly b: number;
} */
Again, depending on your use cases, you may or may not care about such things.
Playground link to code