I first generate suggestion keys with object keys that can be used like this

const data = {
 content: {
  value: "string", 
 },
 anotherContent: {
  anotherValue: 20
 }
}.

const result = function(["content.value","anotherContent.anotherValue"])

First, I wanted the result type to be {value: number, otherValue: number}.

So, I type it like this.

<Target, Suggestion extends DotedKeysType<S>[]>(
    target?: Target extends Suggestion ? Target : Suggestion 
  ): {
    [key in Target extends Suggestion ? Target[number] : string]: Target extends Suggestion 
      ? OutputByKeys<S, key>
      : never;
  };

Everything works fine, except for the following.

  • When the target is content, I can use it as follows
<View>{result.content}</View>
  • And when it is anotherContent.anotherValue, I have to use it like this.
<View>{result["anotherContent.anotherValue"]}</View>

I also realize that even if we made it, we will have a key problem

// for a data like this. Both last keys are named 'value'
const data = {content: {value: 10},anotherContent: {value: 20}}

// Keeping the result as an object override the value key in the object.
// Since objects can not have same key as property
const result = function(["content.value","anotherContent.value"]);
result = {value : 20 // `Override value of content with anotherContent`}

But with array as an result, Each item in the array can have different name. For example

result = [myFirstValue, mySecondValue] = function(["content.value","anotherContent.anotherValue"])

With that the type of myFirstValue must be the type of content.value and the type mySecondValue must be the type of anotherContent.anotherValue.

So far, that's how close I've come.

 <Target, Suggestion extends DotedKeysType<S>[]>(
    target?: Target extends Suggestion ? Target : Suggestion 
  ): OutputByKeys<S, Target extends Sug ? Target[number] : keyof S>[];

This gives me a union type of all the keys in the target array.

result = [value1: string | number, value2: string | number]

Is there a way to get a type like this in case of array?

// type of `content.value` is `string`
// type of `notherContent.anotherValue` is `number`

const result = function(["content.value","anotherContent.anotherValue"]);

result =>  [myFirstValue, mySecondValue]

result type will be [value1: string, value2: number]

And in case of object where last key are different

// type of `content.value` is `string`
// type of `anotherContent.anotherValue` is `number`
const result = function(["content.value","anotherContent.anotherValue"])

result => {value: "string", anotherValue: 20}

result type => {value: string, anotherValue: number}

I do not want an intersection if possible

// i don't want that if possible
result type => [string & number, string & number]

But i want this

result type => [string, number]

I can't predict the user's choice, otherwise I'd just set the array type => [string, number] and my problem would be solved. I've read a lot of answers but I still haven't found the one I need.

I ask for the same thing in different ways. For an object because it might be useful in another project and for array because it's my current need.

2

There are 2 best solutions below

5
Jay On BEST ANSWER

Hope this can help you. result here

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

type GetProperty<T, S> = S extends `${infer K}.${infer R}`
  ? K extends keyof T
    ? GetProperty<T[K], R>
    : any
  : S extends keyof T
  ? T[S]
  : any;

type GetPropertyFromArray<T, A> = Mutable<A> extends [infer K, ...infer R] ? [GetProperty<T, K>, ...GetPropertyFromArray<T, R>] : A;

function fn<T extends {}, A extends {}>(keys: A): GetPropertyFromArray<T, A> {
  return 0 as any;
}

const data = {
  content: {
    value: 'string',
  },
  anotherContent: {
    anotherValue: 20,
  },
};
const keys = ['content.value', 'anotherContent.anotherValue'] as const;
const res = fn<typeof data, typeof keys>(keys);
0
Arnaud On

Based on @jcalz approach from the playground

const data = {
  content: {
    value: "string"
  },
  anotherContent: {
    anotherValue: 20
  }
};

// Important part that solve the problem
type Paths<T> = T extends object ? { [K in keyof T]:
  `${Exclude<K, symbol>}${"" | `.${Paths<T[K]>}`}`
}[keyof T] : never

type DeepIdx<T, K extends string> = K extends `${infer K0}.${infer KR}` ?
  K0 extends keyof T ? DeepIdx<T[K0], KR> : never :
  K extends keyof T ? T[K] : never;

type Data = typeof data;

declare function f<const K extends readonly Paths<Data>[]>(
  ...keys: K
// Important part that solve the problem
): { [I in keyof K]: DeepIdx<Data, K[I]> };

// We get correct type here [string, number]
const [myFirstValue, mySecondValue] = f("content.value", "anotherContent.anotherValue");

I realize that removing generics const keyword of ts_V5 and readonly works

declare function f<K extends Paths<Data>[]>

But removing only readonly make the generics typed not inferred properly.

Huge thanks @jcalz @jay