Search code examples
javascripttypescriptobjectecmascript-2019

Typescript spread prevent overwrite with undefined


When using spread operator , prevent overwriting keys with new value undefined

Consider an object bleh1 and bleh2

const bleh1 = {
  name: "ajnskdas",
  foo: "oof",
  bar: "something"
}

const bleh2 = {
  foo: "oofElse",
  bar: undefined,
  booz: "chilled"
}

bleh2.bar should overwrite key bar only if value is not undefined

const bleh3 = {...bleh1, ...bleh2}
// Actual
// {
//   "name": "ajnskdas",
//   "foo": "oofElse",
//   "bar": undefined,
//   "booz": "chilled"
// }
// Desired 
// {
//   "name": "ajnskdas",
//   "foo": "oofElse",
//   "bar": "something",
//   "booz": "chilled"
// } 

I can do it during runtime with function removeEmpty but type/interface of bleh4 wont have new keys of bleh2

ie bleh4.booz is not inferred by typescript

function removeEmpty(obj: any) {
  return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null));
}
const bleh4 = { ...bleh1, ...removeEmpty(bleh2) }

Solution

  • the main issue you seem to be having is that your untyped bleh1 and bleh2 are incomparable bleh1 says that bar must be a string, bleh2 says bar must be undefined

    when merging the types bar can't be both string and undefined at the same time, which equates to the type never

    however if you type bleh1 and 2 then you can tell it how to match the schemas

    function merge<T1, T2>(a: T1, b: T2): Partial<T1 & T2> {
        const rtn: Partial<T1 & T2> = { ...a };
        for (const [k, v] of Object.entries(b)) {
            if (v) rtn[k as keyof T2] = v;
        }
        return rtn;
    }
    
    const bleh3 = merge(
        {
            name: 'ajnskdas',
            foo: 'oof',
            bar: 'something',
        } as {
            foo: string;
            bar: string | undefined;
            name: string;
        },
        {
            foo: 'oofElse',
            bar: undefined,
            booz: 'chilled',
        } as {
            foo: string;
            bar: string | undefined;
            booz: string;
        }
    );
    console.log(bleh3);