如何将javascript对象减少为仅包含接口的属性

Tom*_*s F 21 javascript angularjs typescript

使用typescript时,声明的接口可能如下所示:

interface MyInterface {
  test: string;
}
Run Code Online (Sandbox Code Playgroud)

具有额外属性的实现可能是这样的:

class MyTest implements MyInterface {
  test: string;
  newTest: string;
}
Run Code Online (Sandbox Code Playgroud)

示例(此处变量'reduced'仍包含属性'newTest'):

var test: MyTest = {test: "hello", newTest: "world"}

var reduced: MyInterface = test; // something clever is needed
Run Code Online (Sandbox Code Playgroud)

一般来说,如何使'reduced'变量只包含'MyInterface'接口中声明的属性.

为什么

尝试在将angular.toJson发送到休息服务之前使用'reduced'变量时会出现问题 - toJson方法会转换newTest变量,即使它在编译期间无法在实例上访问,这也使得其余服务不会接受json,因为它具有不应存在的属性.

wal*_*wal 8

这是不可能的。原因interface是 Typescript 构造并且转译的 JS 代码是空的

//this code transpiles to empty!
interface MyInterface {
  test: string;
}
Run Code Online (Sandbox Code Playgroud)

因此,在运行时没有什么可“使用”的——不存在可供查询的属性。

@jamesmoey 的回答解释了实现预期结果的解决方法。我使用的类似解决方案只是将“接口”定义为一个类 -

class MyInterface {
  test: string = undefined;
}
Run Code Online (Sandbox Code Playgroud)

然后您可以使用lodash从“接口”中选择属性以注入您的对象:

import _ from 'lodash';  //npm i lodash

const before = { test: "hello", newTest: "world"};
let reduced = new MyInterface();
_.assign(reduced , _.pick(before, _.keys(reduced)));
console.log('reduced', reduced)//contains only 'test' property
Run Code Online (Sandbox Code Playgroud)

JSFiddle

这是一个务实的解决方案,它很好地为我服务,而不会陷入关于它是否实际上是接口和/或命名约定(例如IMyInterfaceMyInterface)的语义上,并允许您模拟和单元测试


Vit*_*tin 7

TS 2.1 具有 Object Spread 和 Rest,因此现在可以:

var my: MyTest = {test: "hello", newTest: "world"}

var { test, ...reduced } = my;
Run Code Online (Sandbox Code Playgroud)

之后,reduced 将包含除“test”之外的所有属性。

  • 这有效,但它需要您确切知道哪些属性将是“多余的”。如果你不知道呢? (20认同)

jca*_*alz 6

另一种可能的方法:

正如其他答案所提到的,你无法避免在运行时做一些事情;TypeScript 编译为 JavaScript,主要是通过简单地删除接口/类型定义、注释和断言来实现。类型系统被删除,并且MyInterface在需要它的运行时代码中找不到您的类型系统。

因此,您将需要一些类似要保留在简化对象中的键数组之类的东西:

const myTestKeys = ["test"] as const;
Run Code Online (Sandbox Code Playgroud)

这本身很脆弱,因为如果MyInterface被修改,您的代码可能不会注意到。让代码引起注意的一种可能方法是设置一些类型别名定义,如果myTestKeys与以下内容不匹配,则会导致编译器错误keyof MyInterface

// the following line will error if myTestKeys has entries not in keyof MyInterface:
type ExtraTestKeysWarning<T extends never =
  Exclude<typeof myTestKeys[number], keyof MyInterface>> = void;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type 'UNION_OF_EXTRA_KEY_NAMES_HERE' does not satisfy the constraint 'never'

// the following line will error if myTestKeys is missing entries from keyof MyInterface:
type MissingTestKeysWarning<T extends never =
  Exclude<keyof MyInterface, typeof myTestKeys[number]>> = void;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type 'UNION_OF_MISSING_KEY_NAMES_HERE' does not satisfy the constraint 'never'
Run Code Online (Sandbox Code Playgroud)

这不是很漂亮,但是如果您进行更改MyInterface,上面的一行或两行将给出一个错误,希望该错误具有足够的表现力,以便开发人员可以修改myTestKeys

有多种方法可以使其更加通用,或者可能减少侵入性,但几乎无论您做什么,您可以从 TypeScript 中合理期望的最好结果是,您的代码将在接口发生意外更改时向编译器发出警告;并不是说您的代码实际上会在运行时执行不同的操作。


一旦你拥有了你关心的键,你就可以编写一个pick()函数来从对象中提取这些属性:

function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
  return keys.reduce((o, k) => (o[k] = obj[k], o), {} as Pick<T, K>);
}
Run Code Online (Sandbox Code Playgroud)

我们可以在您的对象上使用它们来test获取reduced

var test: MyTest = { test: "hello", newTest: "world" }

const reduced: MyInterface = pick(test, ...myTestKeys);

console.log(JSON.stringify(reduced)); // {"test": "hello"}
Run Code Online (Sandbox Code Playgroud)

这样可行!

Playground 代码链接