如何在 TypeScript 中增加导入的 json 文件?

How to augment imported json file in TypeScript?

我正在我的 TypeScript 代码中导入一个 JSON 文件(使用版本 4.0.0+),我需要扩充它的定义。

import catalog from '../../../data/app.catalog.json';

我在项目的 TS 根目录下创建了一个文件 global.d.ts 并扩充为:

declare module '*.catalog.json' {
  interface Variant {
    id: string;
    props: {
      [k: string]: string;
    };
    svg: string;
  }

  const value: Variant;

  export default value;
}

然而,TypeScript 不遵守这一点并将 catalog 的类型视为空对象 {}

附带说明一下,当我将文件的扩展名从 json 更改为其他内容时,模块扩充工作正常。不知何故,它对 json 文件使用了自己的推理,而不是采用我提供的定义。

有什么办法可以解决这个问题吗?

你还没有分享你的 JSON 文件,但是——大概——你只是想通知 TS 你的 JSON 实际上是它被推断为的类型的超类型(例如字符串json.props 的键),或者它是至少与实际数据兼容的类型。

如果是这样,那么您可以使用 assertion function 来完成,如下所示:

function assertIsType <T>(value: unknown): asserts value is T {
  if (/* value is not the shape of T */) throw new Error('Invalid type');
  // else value is T and function exits cleanly, implicitly returning void/undefined
}

value 上调用函数后,value 将是类型交集 typeof value & T。如果 typeof valueT 完全不兼容,则编译器将不允许使用您为 T.

提供的泛型进行调用

Warning:

If you don't actually validate in the function body the type that you are asserting, you are lying to the compiler, and it will believe you. I'm going to do this in the example below:

TS Playground

///// File: ../../../data/app.catalog.json
// Because you didn't share your actual JSON file, I'll make up a type
// to represent what TS infers it to be in a typical scenario:

type InferredJson = {
  id: string;
  props: {
    literal1: string;
    literal2: string;
    literal3: string;
    // etc...
  };
  // svg: string; // Let's say this isn't in your actual JSON
};


///// File: ./module.ts
// Instead of the actual import because this the playground doesn't support multiple files:
// import catalog from '../../../data/app.catalog.json';
declare const catalog: InferredJson;

// Won't work because of TS infers literal keys:
catalog.props.someProp = 'some value'; /*
              ^^^^^^^^
Property 'someProp' does not exist on type '{ literal1: string; literal2: string; literal3: string; }'.(2339) */

interface Variant {
  id: string;
  props: Record<string, string>;
  svg: string;
}

function assertIsType <T>(value: unknown): asserts value is T {}

assertIsType<Variant>(catalog);
catalog; // catalog is now type (InferredJson & Variant)
catalog.props.someProp = 'some value'; // ok

console.log(catalog.svg); // undefined
catalog.svg; // string, but this is a lie that you told the compiler, so act responsibly!

总结:

import catalog from '../../../data/app.catalog.json';
import {assertIsType, type Variant} from './someModule';

assertIsType<Variant>(catalog); // Now it's (typeof catalog & Variant)