9
votes

Here is what i wanna do:

const assign = <T extends U, U>(original: T, changes: U) =>
  <T>Object.assign({}, original, changes);

interface IFoo {
  bar: number,
  baz?: string,
  quux?: boolean,
}   

let foo = <IFoo>{ bar: 42, baz: 'hello' };

assign(foo, { baz: 'world' }); //Compilation Error:

I just wanna implement a type safe assign. but the compiler throws the error:

Argument of type 'IFoo' is not assignable to parameter of type '{ baz: string; }'. Property 'baz' is optional in type 'IFoo' but required in type '{ baz: string; }'.

The properties of changes type should always be a strict subset of original type, this kind of assignment should be totally logical, I think.

How do I get around it, appeasing the scrupulous compiler without sacrificing the type safety of the changes parameter?

2

2 Answers

1
votes

Your generics are wrong, if I understand you right.

It should be:

const assign = <T>(original: T, changes: T) => <T> Object.assign({}, original, changes);

interface IFoo {
    bar?: number;
    baz?: string;
    quux?: boolean;
}   

let foo = <IFoo> { bar: 42, baz: 'hello' };

assign(foo, { baz: 'world' });

(code in playground)

Both your original and changes are of the same type, just that each of them has different properties defined in the interface (based on your example of course).
You do not have two different types which one extend the other, so <T extends U, U> is wrong.

Also, since you want to be able to only have subset of the properties in the interface then you must make them all optional, otherwise you'll get compilation errors.


Edit

Based on your comment, this might work for you:

const assign = <T extends U, U>(original: T, changes: U) => <T> Object.assign({}, original, changes);

interface FooOptional {
    baz?: string;
    quux?: boolean;
}

interface FooMandatory extends FooOptional {
    bar: number;
}

let foo = { bar: 42, baz: 'hello' };

assign(foo, { baz: 'world' });

(code in playground)

0
votes

Do you really need T extends U in assign, since you are "only" interested in type checking of U

const assign = <T, U>(original: T, changes: U) => <T> Object.assign({}, original, changes);

U could be defined as

interface U {
     baz: string
}


let foo = <IFoo>{ bar: 42, baz: 'hello' };
let u: U = { baz: 'world' }
assign(foo, u);