I'm sure there is an elegant solution for this typical pattern but I'm having a hard time to understand how to properly create an object incrementally in a type-save way. Basically I just want to create an object with a predefined type by initially creating an empty object and then adding the required properties. (I do understand that creating it at once does not cause this problem but my use case requires incremental creation)
The following examples show the options I have been trying and the problems with each approach:
type myType = {
foo: string,
bar: number,
};
// has errors
function createA(): myType {
const o = {};
o.foo = 'foo'; // Error: Property 'foo' does not exist on type '{}'
o.bar = 0; // Error: Property 'bar' does not exist on type '{}'
return o; // Error: Type '{}' is missing the following properties from type 'myType': foo, bar
}
// has errors
function createB(): myType {
const o: myType = {}; // Type '{}' is missing the following properties from type 'myType': foo, bar
o.foo = 'foo';
o.bar = 0;
return o;
}
// has errors
function createC(): myType {
const o = {};
o['foo'] = 'foo'; // Error: Element implicitly has an 'any' type because type '{}' has no index signature.
o['bar'] = 0; // Error: Element implicitly has an 'any' type because type '{}' has no index signature.
return o; // Error: Type '{}' is missing the following properties from type 'myType': foo, bar
}
// no errors but not type-save
function createD(): myType {
const o: any = {};
o.foo = 'foo';
return o; // No error but object does not contain the required 'bar' property
}
// no errors but not type-save
function createE(): myType {
const o: any = {};
o.foo = 'foo';
return o; // No error but object does not contain the required 'bar' property
}
// no errors but not type-save
function createF(): myType {
const o: myType = <any>{};
o.foo = 'foo';
return o; // No error but object does not contain the required 'bar' property
}
// no errors but not type-save
function createG(): myType {
const oFoo = {foo: 'foo'};
const oBar = {bar: 0};
return Object.assign(oFoo); // No error but object does not contain the required 'bar' property
}
// no errors and type-save but very convolute and expensive for a large number of properties
function createH(): myType {
const foo = 'foo';
const bar = 0;
return {foo, bar};
}
You have two options:
Expandable.This type allows creating objects on an as-needed basis.
interface Expandable {
extend<T>(properties: T): this & T;
}
You start with an empty object and add properties to it when you see fit.
declare const base: Expandable;
const result: MyType =
base
.extend({ foo: 'hello '})
.extend({ bar: 42 });
If you want IntelliSense when you're extending the base, we can do it by making Expandable generic. A possible implementation:
type Slice<T> = { [P in keyof T]: Pick<T, P> }[keyof T];
class Extendable<T> {
extend<U extends Slice<T>>(properties: U): this & U {
return Object.assign(this, properties);
}
}
function create(): MyType {
const base = new Extendable<MyType>();
return base
.extend({ foo: 'hello' })
.extend({ bar: 42 })
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With