admin管理员组文章数量:1391786
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 badges4 Answers
Reset to default 4Answering 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:
- 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 - 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 asany
.
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
本文标签: javascriptTypesafe nested property lookupStack Overflow
版权声明:本文标题:javascript - Typesafe nested property lookup - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744760901a2623749.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论