Optional keys in objects

I have typical problem with javascript. Say, I have an input object, which, in some cases, could be enrich with additional keys. I have two options how to do that. First:

function enrich(input, optionalData) {
  return {
    ...input,
    additionalKey: optionalData
  }
}

Now I could have an object with { additionalKey: undefined }, which could lead to bugs. For example, additionalKey will be lost after JSON.stringify/parse. Another bug: check key with operator in ('additionalKey' in obj), which gives true, but the value is actually undefined → we have to write additional code which checks the value also.

Another option here is

function enrich(input, optionalData) {
  const output = { ...input }

  if (typeof optionalData !== 'undefined') {
    output.additionalKey = optionalData
  }

  return output
}

which gives us 3 more lines of code per key, and also cannot be properly typed with typescript.

What I propose:

function enrich(input, optionalData) {
  return {
    ...input,
    additionalKey?: optionalData
  }
}

if optionalData is undefined, then additionalKey will not be added to the output object.

5 Likes

I usually code like this:

const enrich = (input, optionalData) => ({...input, ...options && {options}})

In general case, it would be

const enrich = (input, optionalData) => ({...input, ...typeof additionalKey !== 'undefined' && {additionalKey: optionalData}})

:(

const enrich = (input, optionalData) => ({
    ...input,
    ...options != null && {options},
})

This is good for a small set of keys, but when there are several of them and additional values are calculated, a special syntax is much more convenient.

Compare

function enrich(target) {
  // we don't want to call calcDerived twice on same value
  const derivedOnTarget = calcDerived(target)
  const derivedOnSome = target.some ? calcDerived(target.some) : void 0
  return {
    length: Object.keys(target).length,
    ...derivedOnTarget !== undefined && { derived: derivedOnTarget},
    ...derivedOnSome !== undefined && { prop: derivedOnSome},
  }
}

vs

function enrich(target) {
  return {
    length: Object.keys(target).length,
    derived?: calcDerived(target),
    prop?: target.some ? calcDerived(target.some) : void 0,
  }
}
2 Likes

Love the idea, don't like the syntax as it's too rigid and not a fan of making it implicitly not add on undefined as it feels too magical.

What about this?

function enrich(target) {
  // we don't want to call calcDerived twice on same value
  const derivedOnTarget = calcDerived(target)
  const derivedOnSome = target.some ? calcDerived(target.some) : void 0
  return {
    length: Object.keys(target).length,
    if (derivedOnTarget !== undefined) {
      derived: derivedOnTarget,
    }
    if (derivedOnSome !== undefined) {
      prop: derivedOnSome,
    }
  }
}

Would also be a fan of allowing for and while loops as well. Note that the bodies here are either key: value or object literals to further mix in (with duplicate keys still syntactically checked).