I'm playing with lookup types and would like to build kind of safe-merge util function (one that takes entity of type T
and an object containing subset of keys of T
to update). My goal is to let compiler tell me when I misspell a property or try to append non-existing one for T
.
So I have Person
and use built-in Partial
in (v2.1) like this:
interface Person {
name: string
age: number
active: boolean
}
function mergeAsNew<T>(a: T, b: Partial<T>): T {
return Object.assign({}, a, b);
}
Now I apply this to the following data:
let p: Person = {
name: 'john',
age: 33,
active: false
};
let newPropsOk = {
name: 'john doe',
active: true
};
let newPropsErr = {
fullname: 'john doe',
enabled: true
};
mergeAsNew(p, newPropsOk);
mergeAsNew(p, newPropsErr); // <----- I want tsc to yell at me here because of trying to assign non-existing props
I would like TS compiler to yell at me on the second invocation as fullname
and enabled
aren't props of Person
. Unfortunately this compiles fine locally, but... when I do the same in online TS Playground I get more or less what I expect:
The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
Type argument candidate 'Person' is not a valid type argument because it is not a supertype of candidate '{ fullname: string; enabled: boolean; }'.
Property 'name' is missing in type '{ fullname: string; enabled: boolean; }'.
Looks like the playground uses the same version as I do locally (2.1.4). Does anybody have a clue why these two may differ?
Bonus question:
when I try the following assignment:
let x: Person = mergeAsNew(p, newPropsOk);
I get the following error on x
but only on the playground (it's all fine locally):
Type '{ name: string; active: boolean; }' is not assignable to type 'Person'.
Property 'age' is missing in type '{ name: string; active: boolean; }'.
Why is that? Shouldn't it be of Person
type, as first mergeAsNew
argument is Person
and and everything else is Person
-props subset (so it's at most Person
)?
EDIT
Here is my tsconfig.json
:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"noEmitOnError": true,
"allowJs": false,
"sourceMap": true,
"strictNullChecks": true
},
"exclude": ["dist", "scripts"]
}
When you want to express that one type has only a subset of properties from another type, plain old
extends
can help tooPS no idea what's going on with playground and mapped types