Reputation: 528
I am trying to implement C# Class object initializer in typescript class. But compiler showing following error.
error : Type 'string | number | null | undefined' is not assignable to type 'null'. Error : Type 'undefined' is not assignable to type 'null'
Below is the code
type gender = 'Male' | 'female';
class Person {
public name: string | null = null;
public age: number | null = null;
public gender: gender | null = null;
constructor(
param: Partial<Person>
) {
if (param) {
/**
* Is key exist in Person
* @param param key
*/
const keyExists = (k: string): k is keyof Person => k in this;
/**
* Keys
*/
const keys: string[] = Object.keys(param);
keys.forEach((k: string) => {
if (keyExists(k)) {
//k: "name" | "age" | "gender"
const value = param[k]; //value: string | number | null | undefined
//let a = this[k]; //a: string | number | null
this[k] = value; //error : Type 'string | number | null | undefined' is not assignable to type 'null'.
// Error : Type 'undefined' is not assignable to type 'null'.
}
});
}
}
}
let a = new Person({
age: 10
});
console.log(a);
and below is the tsconfig.json
{
"compilerOptions": {
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"strict": true, /* Enable all strict type-checking options. */
"strictNullChecks": true, /* Enable strict null checks. */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
}
}
Upvotes: 2
Views: 2215
Reputation: 66093
Your problem is actually two fold:
param
is Partial<Person>
, therefore it may not contain all the keys present. Yet, the keyExists()
method will only inform the compiler if a given key is present in Person
, so you will end up with param[key]
returning undefined
.value
down to a key-by-key basis.The solution might not be the best, but it's what that will work:
param[k]
is undefinedthis[k]
to typeof value
Even though I usually avoid using manual type casting, in this case it's quite reasonable and safe, because you are, at the point of casting, working with k
that must be a member of the Person
class, so you are simply hinting the compiler that "I know what specific type I am working with, don't panic".
With that, the modified logic inside the Array.prototype.forEach
callback looks like this:
keys.forEach((k: string) => {
if (keyExists(k)) {
const value = param[k];
if (value === void 0)
return;
(this[k] as typeof value) = value;
}
});
See it on TypeScript Playground.
Personally I don't like nested if
statements because it makes the code really difficult to read, so you can also refactor the above code to use guard clauses exclusively:
keys.forEach((k: string) => {
if (!keyExists(k))
return;
const value = param[k];
if (value === void 0)
return;
(this[k] as typeof value) = value;
});
See on TypeScript Playground.
Upvotes: 3