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).

1 Like

Dart has a similar idea to @claudiameadows proposal, called "collection if" and "collection for", but it only works on array.

Example from Dart's website:

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];

Such syntax would be nice in javascript too.

+1 for this topic. Frequently I have to write cumbersome code like this:

{ ...input, ...additionalKey !== undefined && { [additionalKey]: optionalData } }

This seems too long compared to what it does. The motivation here is the shortness of the code. @claudiameadows idea sounds reasonable but doesn't do much for this.

There're two concerns: emptiness of "key", and of "value". We need separate syntax for each of them. As ECMAScript already introduced "nullish" semantics to its syntax (e.g. optional chaining), we should use that here too, for example:

{ [key]?: value }

means

{ ...(key !== undefined && key !== null ? { [key]: value } : {}) }

and

{ [key]:? value }

means

{ ...(value !== undefined && value !== null ? { [key]: value } : {}) }
1 Like

Can't say I'm fond of the implicitness of the syntax. It'd be easy to overlook a simple ? before : when studying the structure of a created object and then subsequently wonder how that property doesn't still exist, and I've already run into similar situations in the past from misreading typos with object keys.

1 Like