简体   繁体   English

如何减少 javascript object 只包含来自接口的属性

[英]How to reduce javascript object to only contain properties from interface

When using typescript a declared interface could look like this:使用 typescript 时,声明的接口可能如下所示:

interface MyInterface {
  test: string;
}

And an implementation with extra property could be like this:具有额外属性的实现可能是这样的:

class MyTest implements MyInterface {
  test: string;
  newTest: string;
}

Example (here the variable 'reduced' still contain the property 'newTest'):示例(这里的变量 'reduced' 仍然包含属性 'newTest'):

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

var reduced: MyInterface = test; // something clever is needed

Question问题

In a general way, how can you make the 'reduced' variable to only contain the properties declared in the 'MyInterface' interface.一般来说,如何使“简化”变量只包含在“MyInterface”接口中声明的属性。

Why为什么

The problem occur when trying to use the 'reduced' variable with angular.toJson before sending it to a rest service - the toJson method transforms the newTest variable, even if it's not accessible on the instance during compile, and this makes the rest service not accept the json since it has properties that shouldn't be there. The problem occur when trying to use the 'reduced' variable with angular.toJson before sending it to a rest service - the toJson method transforms the newTest variable, even if it's not accessible on the instance during compile, and this makes the rest service not接受 json 因为它具有不应该存在的属性。

It is not possible to do this.这是不可能的。 The reason being interface is a Typescript construct and the transpiled JS code is empty interface的原因是 Typescript 结构, 转译后的 JS 代码为空

//this code transpiles to empty!
interface MyInterface {
  test: string;
}

Thus at runtime there is nothing to 'work with' - no properties exist to interrogate.因此在运行时没有什么可以“使用” - 不存在要询问的属性。

The answer by @jamesmoey explains a workaround to achieve the desired outcome. @jamesmoey 的回答解释了实现预期结果的解决方法。 A similar solution I use is simply to define the 'interface' as a class -我使用的类似解决方案只是将“接口”定义为一个类 -

class MyInterface {
  test: string = undefined;
}

Then you can use lodash to pick the properties from the 'interface' to inject into you object:然后您可以使用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

see JSFiddleJSFiddle

This is a pragmatic solution that has served me well without getting bogged down on semantics about whether it actually is an interface and/or naming conventions (eg IMyInterface or MyInterface ) and allows you to mock and unit test这是一个务实的解决方案已经使我受益匪浅,而不陷入困境关于是否实际上是一个接口和/或命名约定(如语义IMyInterfaceMyInterface ),并允许你嘲笑和单元测试

TS 2.1 has Object Spread and Rest, so it is possible now: TS 2.1 具有 Object Spread 和 Rest,因此现在可以:

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

var { test, ...reduced } = my;

After that reduced will contain all properties except of "test".之后,reduce 将包含除“test”之外的所有属性。

Another possible approach:另一种可能的方法:

As other answers have mentioned, you can't avoid doing something at runtime;正如其他答案所提到的,您无法避免在运行时做某事; TypeScript compiles to JavaScript, mostly by simply removing interface/type definitions, annotations, and assertions. TypeScript 编译为 JavaScript,主要是通过简单地删除接口/类型定义、注释和断言。 The type system is erased , and your MyInterface is nowhere to be found in the runtime code that needs it.类型系统已删除,并且您的MyInterface在需要它的运行时代码中无处可寻。

So, you will need something like an array of keys you want to keep in your reduced object:因此,您将需要像要保留在缩减对象中的键数组之类的东西:

const myTestKeys = ["test"] as const;

By itself this is fragile, since if MyInterface is modified, your code might not notice.这本身MyInterface脆弱,因为如果修改了MyInterface ,您的代码可能不会注意到。 One possible way to make your code notice is to set up some type alias definitions that will cause a compiler error if myTestKeys doesn't match up with keyof MyInterface :让您的代码注意到的一种可能方法是设置一些类型别名定义,如果myTestKeyskeyof 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'

That's not very pretty, but if you change MyInterface , one or both of the above lines will give an error that hopefully is expressive enough that the developer can modify myTestKeys .这不是很漂亮,但是如果您更改MyInterface ,则上述一行或两行将给出一个错误,希望该错误具有足够的表现力,开发人员可以修改myTestKeys

There are ways to make this more general, or possibly less intrusive, but almost no matter what you do, the best you can reasonably expect from TypeScript is that your code will give compiler warnings in the face of unexpected changes to an interface;有一些方法可以使这更通用,或者可能更少干扰,但几乎无论你做什么,你可以合理地从 TypeScript 中得到的最好的期望是,你的代码会在面对接口的意外更改时发出编译器警告; not that your code will actually do different things at runtime.并不是说您的代码实际上会在运行时做不同的事情。


Once you have the keys you care about you can write a pick() function that pulls just those properties out of an object:一旦你有了你关心的键,你就可以编写一个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>);
}

And them we can use it on your test object to get reduced :我们可以在您的test对象上使用它们来reduced

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

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

console.log(JSON.stringify(reduced)); // {"test": "hello"}

That works!这样可行!

Playground link to code Playground 代码链接

您是否仅尝试仅设置\/分配界面上列出的属性? TypeScript 中没有类似的功能,但编写一个函数来执行您寻找的行为非常简单。

 interface IPerson { name: string; } class Person implements IPerson { name: string = ''; } class Staff implements IPerson { name: string = ''; position: string = ''; } var jimStaff: Staff = { name: 'Jim', position: 'Programmer' }; var jim: Person = new Person(); limitedAssign(jimStaff, jim); console.log(jim); function limitedAssign<T,S>(source: T, destination: S): void { for (var prop in destination) { if (source[prop] && destination.hasOwnProperty(prop)) { destination[prop] = source[prop]; } } }<\/code><\/pre>

"

In your example newTest property won't be accessible thru the reduced variable, so that's the goal of using types.在您的例子newTest财产不会通的减少变量访问,所以这是使用类型的目标。 The typescript brings type checking, but it doesn't manipulates the object properties.打字稿带来了类型检查,但它不操纵对象属性。

In a general way, how can you make the 'reduced' variable to only contain the properties declared in the 'MyInterface' interface.一般来说,如何使“简化”变量只包含在“MyInterface”接口中声明的属性。

Since TypeScript is structural this means that anything that contains the relevant information is Type Compatible and therefore assignable.由于 TypeScript 是结构化的,这意味着任何包含相关信息的内容都是类型兼容的,因此是可分配的。

That said, TypeScript 1.6 will get a concept called freshness .也就是说,TypeScript 1.6 将获得一个名为freshness的概念。 This will make it easier to catch clear typos (note freshness only applies to object literals):这将更容易捕捉清晰的拼写错误(注意新鲜度仅适用于对象文字):

// ERROR : `newText` does not exist on `MyInterface`
var reduced: MyInterface = {test: "hello", newTest: "world"}; 

Easy example:简单的例子:

let all_animals = { cat: 'bob', dog: 'puka', fish: 'blup' };
const { cat, ...another_animals } = all_animals;
console.log(cat); // bob

One solution could be to use a class instead of an interface and use a factory method (a public static member function that returns a new object of it's type).一种解决方案可能是使用class而不是interface并使用factory method (返回其类型的新对象的公共静态成员函数)。 The model is the only place where you know the allowed properties and it's the place where you don't forget to update them accidentaly on model changes.模型是您知道允许的属性的唯一地方,也是您不会忘记在模型更改时意外更新它们的地方。

class MyClass {
  test: string;

  public static from(myClass: MyClass): MyClass {
    return {test: myClass.test};
  }
}

Example:例子:

class MyTest extends MyClass {
  test: string;
  newTest: string;
}

const myTest: MyTest = {test: 'foo', newTest: 'bar'};
const myClass: MyClass = MyClass.from(myTest);

Possibly a duplicate of: 可能是以下副本:

TypeScript or JavaScript type casting TypeScript或JavaScript类型转换

You have to "cast" your value to a different type. 您必须将您的值“转换”为其他类型。

some good examples are also here: http://blogs.microsoft.co.il/gilf/2013/01/18/using-casting-in-typescript/ 一些很好的例子也在这里: http//blogs.microsoft.co.il/gilf/2013/01/18/using-casting-in-typescript/

this.span = <HTMLSpanElement>document.createElement('span');

hope this helped? 希望这有帮助吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM