1

I have the following interfaces:

interface TxInterface {
    field1: string;
    field2: string;
    field3: number;
}

interface RxInterface {
    field1: string;
    field2: string;
    field3: number;
    field4: string;
    field5: string;
}

When I receive an object from server, it is typed with RxInterface. In my App, I continue to use this object when I update values for instance.

When I would like to update object in server, I shall send "TxInterface", that contains some fields of "RxInterface".

So, how can I easily to merge my object from RxInterface to TxInterface before send and take only the fields of TxInterface?

clem
  • 799
  • 9
  • 20
  • Would you clarify the last part? If you need to send only TxInterface fields to the server then it won't be a merge, more like a reduce of the larger object to the smaller one. – hazardous Apr 22 '19 at 08:59
  • Possible duplicate of [How to take a subset of an object using an interface?](https://stackoverflow.com/questions/43028324/how-to-take-a-subset-of-an-object-using-an-interface) – jcalz Apr 22 '19 at 14:33
  • Possible duplicate of [Convert an object of an interface to its base interface](https://stackoverflow.com/questions/55682572/convert-an-object-of-an-interface-to-its-base-interface) – jcalz Apr 22 '19 at 14:34
  • Possible duplicate of [TypeScript - extract interface members only - possible?](https://stackoverflow.com/questions/50839597/typescript-extract-interface-members-only-possible) – jcalz Apr 22 '19 at 14:34

2 Answers2

1

There's no way to do this merely with the interface definitions, since such definitions are part of the type system which is erased by runtime. If you need something done at runtime, you need to write code which does it at runtime. In this case, here's a generic function which extracts just the properties of an object matching a list of keys:

function extract<T extends object, K extends keyof T>(
    obj: T,
    keys: K[]
): Pick<T, K> {
    const ret = {} as Pick<T, K>;
    keys.forEach(k => ret[k] = obj[k])
    return ret;
}

And for your specific use case, you can make a conversion function whose types involve RxInterface and TxInterface, but which specifies the particular keys you care about as values and not types:

const rxToTx = (rx: RxInterface): TxInterface => // type annotation here
    extract(rx, ["field1", "field2", "field3"]); // explicit key values here

And you can verify that it works:

const rx: RxInterface = {
    field1: "yes",
    field2: "yep",
    field3: 100,
    field4: "no",
    field5: "nope"
}

const tx = rxToTx(rx);

console.log(tx);
// {field1: "yes", field2: "yep", field3: 100}

Hope that helps; good luck!

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Your solution works but warnings are raised and I can't find a solution. The head warning is "Argument type [string , string , string] is not assignable to parameter type keyof T[] ". I works with PhpStorm. – clem Apr 22 '19 at 18:30
  • Uh, I can't reproduce your issue. If you are using TS3.4+ you can try `["field1", "field2", "field3"] as const`, but I think it should work as stated. Do you have a way of producing a [mcve] which demonstrates your problem? If I can't reproduce it it's hard to give advice. – jcalz Apr 22 '19 at 19:07
  • I use TS3.2 in Angular 7 app. Minimal example can be "Create Angular 7 project" then "Add the snippets in app.component.ts" – clem Apr 23 '19 at 17:18
  • I don't have PhpStorm, so unless your example can be reproduced via a link to some web IDE like [this](https://typescript-play.js.org/) or [this](https://repl.it/languages/typescript), I don't think I can be of much help here. And if you suspect that your issue is specific to PhpStorm, you should add that tag to your question... (maybe a new question, since I doubt you'll get much eyes on this one if you just edit it) – jcalz Apr 23 '19 at 17:23
0

All right. It is a good starting point to introduce binary operations also known as "reductions".

Consider generic function signature: T -> T -> T. What it does is takes two parameters of the same type T and somehow "collapses" them into a single resulting instance of the same type T. As a trivial example, take + on numbers or concatenation on the strings.

It turns out that such a binary operation is unbelievably common across various branches of math and founded on a much more reliable reasons then just a programming convenience. For example, a "semigroup" is essentially the same binary operation with few additional properties, I won't explain here.

So, don't ignore it. Rather design you code in such a way that you can leverage binary operations of the abovementioned (generic) signature. The question is: how? Mapping! Have you ever heard something about "map/reduce" approach? That's exactly what you are about to implement: firstly map a non-reducible instance into a reducible one and then apply one of reductions you have in order to compute final result.

P.S. In your case mapping since to be trivial: just specify explicitly generic params, since a wider interface is also satisfies narrower interface.

Zazaeil
  • 3,900
  • 2
  • 14
  • 31