Home > OS >  Assign/Copy only specified Attributes to Object (TS/JS)
Assign/Copy only specified Attributes to Object (TS/JS)

Time:08-13

Would it be possible to have a copy operation like Object.assign(...) to copy only known Properties to the destination?

My sample code looks like:

class A {
    foo?: string;
    constructor(p: any) {
        Object.assign(this, p);
    }
}

const instance = new A({
    foo: 'test',
    bar: 'other'
});

console.log(instance); // yields:     A: { "foo": "test", "bar": "other" }
                       // but i want: A: { "foo": "test" }

I know that typings are removed in JS but wonder if it would still be possible with something like decorators.

Checking with .hasOwnProperty or similar is not an option because it should allow copy of unset properties like in the example above.

CodePudding user response:

There is no concept of reflection or introspection in TypeScript. So one approach would be to add metadata to your type and roll your own version of assign that uses it.

type TypeMap<T> = Record<keyof T, 'string' | 'number' | 'bigint' | 'boolean' | 'function' | 'object'>;

function customAssign<T>(obj: T, typeMap: TypeMap<T>, data: any): void {
  Object.entries(data)
    .filter(([key, value]) => typeMap[key as keyof T] === typeof value)
    .forEach(([key, value]) => (obj as any)[key] = value);
}
class A {
    foo?: string;
    another: number = 1; // added for testing

    static readonly aTypes: TypeMap<A> = {
      foo: 'string',
      another: 'number',
    };

    constructor(data: any) {
      customAssign(this, A.aTypes, data);
    }
}

const instance = new A({
    foo: 'test',
    bar: 'other'
}); 

console.log(instance);  // outputs {another: 1, foo: 'test'} // another defaults to 1 and not accidentally overwritten.

The solution encompasses the following features.

  • A TypeMap type that is keyed by a type's keys and the result of typeof when then values are assigned.
  • A customAssign function that works like assign but also incorporates TypeMap so that the values are properly assigned.
  • A static TypeMap instance that corresponds to A's properties is added to A.
  • Object.assign is replaced in the constructor with customAssign.

The output matches your desired output. Additionally values won't get overwritten that were assigned by default.

CodePudding user response:

If you want the resulting object to keep only the properties of the A class, then you can try this:

class A {
    foo?: string;

    constructor(p: A | any) {
        this.foo = typeof p === 'undefined' || typeof p.foo !== 'string' ? '' : p.foo;
    }
}

For a class with many properties:

class A {
    foo?: string;
    bar?: string;
    foo1!: number;
    bar1!: number;

    constructor(p: A | any) {
        Object.getOwnPropertyNames(this).forEach(key => {
            if (typeof p !== 'undefined' && typeof p[key] !== 'undefined') {
                this[key] = p[key];
            }
        });
    }
}
  • Related