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

时间:2018-11-28 12:29:35

标签: typescript

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

import postalCodes from '../PostalCodes.json';

它具有以下格式:

{
    "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

我想这样使用它:

// first.postalCode is of type string
const x = postalCodes[first.postalCode];

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

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

我目前最好的方法是创建一个中间ts文件,例如:

import postalCodes from './PostalCodes.json';

export const PostalCodesLookup = postalCodes as {
    [key: string]: { code: number, city: string, isPoBox: boolean }
};

3 个答案:

答案 0 :(得分:2)

自TypeScript v2.9起,您可以在 tsconfig.json 文件的resolveJsonModule中启用compilerOprions标志,如下所示:

{
  "compilerOptions": {
    // ... other options
    "resolveJsonModule": true
  },
}

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

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

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

  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"'.
    ];
    

    取决于用法的“硬编码”方式,导出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
    }
    

Typed Autocompletion example

答案 1 :(得分:2)

我认为您的主要问题不是编译器需要以某种不同的方式来推断postalCodes的类型,而是我们不知道first.postalCode是{{1的键之一}}。由于postalCodes的类型为first.postalCode,因此编译器对此发出警告是合理的。

因此,您需要执行某种type guard才能说服编译器将stringfirst.postalCode缩小到string。我认为任何内置的控制流类型防护都不会为您进行这种缩小(在某些情况下,keyof typeof postalCodesact as a type guard会缩小first.postalCode in postalCodes的类型,幸运的是,您可以实现user-defined type guard来提供您想要的行为:

postalCodes

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

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

请注意,如果您仅了解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 } ,那么您确实必须处理first.postalCode不是postalCodes的键之一的情况。它是first.postalCode

注意事项:string通常不是完全类型安全的。在TypeScript中,值isKeyOf(obj, key)的属性可能比编译器在obj中所知道的更多。也就是说,类型不是exact。在最极端的示例中,如果将keyof typeof obj声明为类型obj,则{}就是keyof typeof obj,尽管事实上never可能具有属性。这就是为什么让obj返回Object.keys(obj)>的common request总是rejected的原因。

幸运的是,对于带有Array<keyof typeof obj之类的推断类型的对象文字而言,此警告不是 问题。那是因为您可以肯定postalCodes是正确的;无需担心其他属性。通常,将typeof postalCodes缩小为key并不安全,如

所示

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

答案 2 :(得分:1)

最近,您还可以对 JSON 文件使用 dynamic imports

像这样:

const postalCodes: { [key: string]: { code: number, city: string, isPoBox: boolean } } = await import('../PostalCodes.json');
const x = postalCodes[first.postalCode]; // should work just fine here now

或使用显式接口或类型声明(我更喜欢):

interface PostalCodes {
  [key: string]: { code: number, city: string, isPoBox: boolean }
}

const postalCodes: PostalCodes = await import('../PostalCodes.json');
const x = postalCodes[first.postalCode]; // should work just fine here now

除了将 resolveJsonModule 编译器选项设置为 true 之外,您还应该为 module 编译器选项使用支持的值。在撰写此答案时,TypeScript 中的动态导入似乎支持模块类型 es2020esnextcommonjsamdsystemumd