javascript - Typesafe nested property lookup - Stack Overflow

I am trying to create a type safe version of the get lodash function (see here). My idea is to create a

I am trying to create a type safe version of the get lodash function (see here). My idea is to create a function that is able to return the correct type if the nested property is available otherwise undefined.

interface Nested {
  value: 'some' | 'type';
}
interface SomeComplexType {
  foo: string;
  bar: number;
  nested: Nested;
}

const testObject: SomeComplexType = {
  foo: 'foo value',
  bar: 1234,
  nested: {
    value: 'type'
  }
};

// The type of someValue should be: 'some' | 'type'
const someValue = lookup(testObject, 'nested.value');
console.log('Value found:', someValue);

Right now I have the following:

function get<T, K extends keyof T>(object: T, key: K): T[K] | undefined {
  return object[key];
}

function lookup<T, K extends keyof T>(object: T, path: string) {
  const parts = path.split('.');
  const property = parts.shift() as K; // TODO autoinfer is possible?
  const value = get(object, property);

  if (!parts.length || value === undefined) {
    return value;
  }

  const newPath = parts.join('.');
  return lookup(value, newPath);
}

But I am stuck with the lookup return type. Typescript in strict mode says:

src/lookup.ts:14:10 - error TS7023: 'lookup' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.

Any ideas?

I am trying to create a type safe version of the get lodash function (see here). My idea is to create a function that is able to return the correct type if the nested property is available otherwise undefined.

interface Nested {
  value: 'some' | 'type';
}
interface SomeComplexType {
  foo: string;
  bar: number;
  nested: Nested;
}

const testObject: SomeComplexType = {
  foo: 'foo value',
  bar: 1234,
  nested: {
    value: 'type'
  }
};

// The type of someValue should be: 'some' | 'type'
const someValue = lookup(testObject, 'nested.value');
console.log('Value found:', someValue);

Right now I have the following:

function get<T, K extends keyof T>(object: T, key: K): T[K] | undefined {
  return object[key];
}

function lookup<T, K extends keyof T>(object: T, path: string) {
  const parts = path.split('.');
  const property = parts.shift() as K; // TODO autoinfer is possible?
  const value = get(object, property);

  if (!parts.length || value === undefined) {
    return value;
  }

  const newPath = parts.join('.');
  return lookup(value, newPath);
}

But I am stuck with the lookup return type. Typescript in strict mode says:

src/lookup.ts:14:10 - error TS7023: 'lookup' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.

Any ideas?

Share Improve this question asked Mar 15, 2019 at 15:20 VoloxVolox 1,1161 gold badge13 silver badges23 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 4

Answering my own question after the introduction of template literal types.

Now with template literal types you can obtain exactly what I wanted.

See example in playground.

See here for the implementation (from this tweet): https://github./ghoullier/awesome-template-literal-types#dot-notation-string-type-safe

So now it works (I renamed the function from get to lookup)

Unfortunately, what you're trying to do is a bit beyond TypeScript's abilities, at least as of this writing. The main issues are:

  1. TypeScript isn't going to be able to figure out what exactly you're doing with that string. TypeScript can check if a given string literal matches an object's keys, but beyond that it can't help. so we can't fix your as K; // TODO autoinfer is possible? ment
  2. You're getting TS7023 because you're recursing down into the object, and since we could be recursing anywhere into the object, TypeScript isn't sure where you're going to stop, so it's forced to infer the return type as any.

To solve #2 above, we can try changing the function declaration to this:

function lookup<T, K extends keyof T>(object: T, path: string): T[K] | undefined {

But then TypeScript will throw TS2322: Type 'T[K][keyof T[K]] | undefined' is not assignable to type 'T[K] | undefined' on our return statement. Essentially TypeScript is saying "the nested child types aren't the same as the parent types, so what you wrote is wrong".

That said, others before you have attempted this, and some have gotten fairly close, though as far as I can tell there isn't a perfect implementation. You can read some various solutions here in this GitHub issue: https://github./Microsoft/TypeScript/issues/12290 Most of them resort to using arrays rather than strings, since TypeScript can then see the individual pieces more easily.

Aside

I wanted to mention, the problem you're trying to solve doesn't quite make sense in the world of TypeScript. If you had a definite definition of the object you're trying to access - Just access it directly! No need for an extra level of indirection. TypeScript will type-check the way you access the object.

If you need to dynamically return a specific field at a later point, you can create an anonymous function somewhere and call it later on to do so, and TypeScript can type-check your anonymous function.

If you truly need dynamic string-based access into a random object you don't have typings for, then any is actually the correct return type for your lookup function. Since if your code doesn't have a definite guarantee of what the type is, it probably really could be anything.

I tried some different ideas, including: https://github./pimterry/typesafe-get and I though I could share my own look on the issue.

Instead of strings I use arrow function wrapped in try/catch to resolve to fallback. This way I can look up both accessor arguments and return type and have opportunity to provide my own fallback:

type CallbackFn<T extends any[] = [], R extends any = void> = (...args: T) => R;

const get = <T extends any, R extends any, F extends any = null>(obj: T, accessor: CallbackFn<[T], R>, fallback: F = null): R | F => {
  if (!obj || typeof accessor !== 'function') {
    return fallback;
  }

  try {
    return accessor(obj);
  } catch (e) {
    return fallback;
  }
};

Added git repo for this with more details: https://github./jan-rycko/get-by-accessor

It looks like the key to making your function possible is for TypeScript to apply the type lookup operation (T[K]) an arbitrary number of times to resolve types like T[K][U][V]. This would require that the piler be able to execute the code itself (a la prepack). I just don't think that's possible at the moment. I think you might have to make do with manually calling the get functions the appropriate number of levels to extract the type you want. For example, the type of out here is 'some' | 'type' as expected.

const out = get(get(testObject, 'nested'), 'value');

Playground link

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744760901a4592168.html

相关推荐

  • javascript - Typesafe nested property lookup - Stack Overflow

    I am trying to create a type safe version of the get lodash function (see here). My idea is to create a

    18小时前
    50

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信