在Typescript中,如何导入json并动态按键查找?

asg*_*las 8 typescript

在打字稿 3.0.3 中,我导入了一个这样的 json 文件:

import postalCodes from '../PostalCodes.json';
Run Code Online (Sandbox Code Playgroud)

它有这样的格式:

{
    "555": { "code": 555, "city": "Scanning", "isPoBox": true },
    "800": { "code": 800, "city": "Høje Taastrup", "isPoBox": true },
    "877": { "code": 877, "city": "København C", "isPoBox": true },
    "892": { "code": 892, "city": "Sjælland USF P", "isPoBox": true },
    "893": { "code": 893, "city": "Sjælland USF B", "isPoBox": true },
    "897": { "code": 897, "city": "eBrevsprækken", "isPoBox": true },
    "899": { "code": 899, "city": "Kommuneservice", "isPoBox": true },
    "900": { "code": 900, "city": "København C", "isPoBox": true },
    "910": { "code": 910, "city": "København C", "isPoBox": true },
    "917": { "code": 917, "city": "Københavns Pakkecenter", "isPoBox": true },

... and so on
Run Code Online (Sandbox Code Playgroud)

我想像这样使用它:

// first.postalCode is of type string
const x = postalCodes[first.postalCode];
Run Code Online (Sandbox Code Playgroud)

我收到错误消息:“元素隐式具有 'any' 类型,因为类型 '...非常长的类型签名...' 没有索引签名。”

有没有办法让它与自动生成的 json 类型一起使用,以便我可以通过它的字符串键动态查找邮政编码?

我现在最好的方法是拥有一个中间 ts 文件,例如:

import postalCodes from './PostalCodes.json';

export const PostalCodesLookup = postalCodes as {
    [key: string]: { code: number, city: string, isPoBox: boolean }
};
Run Code Online (Sandbox Code Playgroud)

hlf*_*rmn 12

由于打字稿V2.9您可以启用resolveJsonModule的标志compilerOprions您的tsconfig.json像这样的文件:

{
  "compilerOptions": {
    // ... other options
    "resolveJsonModule": true
  },
}
Run Code Online (Sandbox Code Playgroud)

现在 TypeScript 应该会自动解析导入的 json 文件中的类型。

要解决索引类型问题,我可以建议两个选项:

  1. suppressImplicitAnyIndexErrors在您的tsconfig.json 中启用。这将抑制此错误消息。你也不会得到任何类型提示。

  2. 为 JSON 创建一些类型,并使用它们而不仅仅是string

    import codes from '../codes.json';
    type PostalCode = keyof typeof codes;
    
    const goodSring: string = '555';
    const badString: string = '2';
    const goodCode: PostalCode = '555';
    const badCode: PostalCode = '2'; // Error:(39, 7) TS2322: Type '"2"' is not assignable to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"'.
    const array: [] = [];
    const obj = {some: 'prop'};
    const num: number = 123;
    
    const list: PostalCode[] = [
        '555',
        '2', // Error:(43, 5) TS2322: Type '"2"' is not assignable to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"'.
        goodCode,
        badCode,
        goodSring, // Error:(46, 5) TS2322: Type 'string' is not assignable to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"'.
        badString, // Error:(47, 5) TS2322: Type 'string' is not assignable to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"'.
        goodSring as PostalCode,
        badString as PostalCode, // no protection here
    
        array as PostalCode, // Error:(54, 13) TS2352: Conversion of type '[]' to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '[]' is not comparable to type '"917"'.
        num as PostalCode, // Error:(55, 13) TS2352: Conversion of type 'number' to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
        obj as PostalCode, // Error:(56, 13) TS2352: Conversion of type '{ some: string; }' to type '"555" | "800" | "877" | "892" | "893" | "897" | "899" | "900" | "910" | "917"' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '{ some: string; }' is not comparable to type '"917"'.
    ];
    
    Run Code Online (Sandbox Code Playgroud)

    根据用法的“硬编码”程度,导出PostalCode类型可能对您很有效。

    您还可以编写一个在运行时检查 JSON 的函数:

    import codes from '../codes.json';
    export type PostalCode = keyof typeof codes;
    
    function verifyCode(s: string): s is PostalCode {
        return codes[s as PostalCode] !== undefined; // or use Object.keys, or some other method
    }
    
    let city: string;
    const str: string = 'asd';
    
    if (verifyCode(str)) {
        city = codes[str].city; // in this branch `str` is `PostalCode`
    } else {
        city = codes[str].city; // complains about the index signature
    }
    
    Run Code Online (Sandbox Code Playgroud)

类型化自动完成示例


jca*_*alz 5

我认为您在这里的主要问题不是编译器需要以postalCodes某种不同的方式推断类型,而是first.postalCode不知道这是postalCodes. 由于first.postalCodeis 是 type string,编译器警告你这一点是合理的。

所以,你需要做某种类型的后卫说服编译器缩小first.postalCodestringkeyof typeof postalCodes。我认为任何内置的控制流类型保护都不会为你做这种缩小(在某些情况下first.postalCode in postalCodes确实充当类型保护,但只是缩小 的类型postalCodes,这不是你想要的。)幸运的是你可以实现用户定义的类型保护来为您提供您正在寻找的行为:

function isKeyof<T extends object>(obj: T, possibleKey: keyof any): possibleKey is keyof T {
  return possibleKey in obj;
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以按如下方式使用它:

declare const first: {postalCode: string};
if (isKeyof(postalCodes, first.postalCode)) {
  const x = postalCodes[first.postalCode];  // no error  
} else {
  // uh oh, first.postalCode is not valid
}
Run Code Online (Sandbox Code Playgroud)

请注意,您确实必须处理first.postalCode不是 的键之一的情况postalCodes,这是您真正应该做的事情,如果您只知道first.postalCode它是string.

警告:isKeyOf(obj, key)通常不是完全类型安全的。在 TypeScript 中,一个值obj可能拥有比编译器知道的更多的属性 in keyof typeof obj。也就是说,类型不精确。在最极端的例子中,如果obj声明为 type {},则keyof typeof objis never,尽管它obj可能具有属性。这就是为什么常见的请求Object.keys(obj)回报Array<keyof typeof obj>总是拒绝

幸运的是,这需要注意的是不能用于推断类型,如对象文本的问题postalCodes。那是因为您确定这typeof postalCodes是准确的;无需担心额外的属性。一般来说,缩小keykeyof typeof obj是不安全的,如图所示

希望有所帮助;祝你好运!