Search code examples
typescripttypescript-types

Initializing TypeScript objects only with their class-specific properties using partials


Consider the following example:

const assign = <T, K extends keyof T>(...args: T[]): T =>
    args.reduce((result, current) =>
        (Object.keys(current) as K[]).reduce((target, key) => {
            target[key] = current[key];
            return target;
        }, result)
        , args[0]);

class Base {
    str1: string = '';
    str2: string = '';

    constructor(data: Partial<Base>) {
        assign(this, data);
    }
}

class Derived extends Base {
    str3: string = '';

    constructor(data: Partial<Derived>) {
        super(data);
        Object.assign(this, data);
    }
}

const derivedObj = new Derived({ str1: 'text', str3: 'new prop' })
const baseObject = new Base(derivedObj);

console.log(baseObject);
// Outputs:
// Data { "str1": "text", "str2": "", "str3": "new prop" } 

The base class comprises less properties than the derived class. The base class constructor accepts a Partial<Base> which is also compatible with the Partial<Derived> type. This results in extraneous properties being assigned to the base class object when passing an object of type Partial<Derived>.

Is there a way to initialize the base class object only with its class-specific properties (str and str2 and not str3)?

PS: There are a lot of seemingly similar discussions on GitHub and here. So far, I haven't found any which tackles this exact problem.


Solution

  • This works:

    const assign = <T, K extends keyof T>(...args: T[]): T =>
        args.reduce((result, current) =>
            (Object.keys(args[0]) as K[]).reduce((target, key) => {
                if (current[key] !== undefined) target[key] = current[key];
                return target;
            }, result)
        , args[0]);
    

    See this playground