1
\$\begingroup\$

I have an array of nested objects. How do I get an object with null values using only a particular key in the array of objects?

let headers = [{
    title: 'Arun',
    id: 'arunId',
    onClick: 'onClickArun'
  },
  {
    title: "George",
    id: 'georgeId',
    onClick: '',
    children: [{
        title: 'David',
        id: 'davidId',
        onClick: 'onClickDavid'
      },
      {
        title: 'Patrick',
        id: 'patrickId',
        onClick: 'onClickPatrick'
      }
    ]
  },
  {
    title: 'Mark',
    id: 'markId',
    onClick: 'onClickMark'
  }
]

const headersMap = ({
  onClick,
  children
}) => (onClick ? {
  onClick
} : _.map(children, headersMap));

const headersFlatMap = _.flatMap(headers, headersMap);

const headerObj = _.reduce(_.map(headersFlatMap, 'onClick'), (ac, a) => ({ ...ac,
  [a]: null
}), {});

console.log(headerObj)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

The code works good, but is there a way to optimize it?

Expected Output:

{
  "onClickArun": null,
  "onClickDavid": null,
  "onClickPatrick": null,
  "onClickMark": null
}

Any help is greatly appreciated. Thanks.

\$\endgroup\$
3
\$\begingroup\$

Performance.

  • map and flatMap create copies of the arrays.
  • For each new item added to the result you rebuild the whole results object ({ ...ac, [a]: null})
  • For each property in the result you create a temp object to hold the key value pair.

These CPU, memory allocation, and GC burdens that can be avoided.

Some will argue that the overhead for small data sets is trivial and thus inconsequential.

Performance always matters and coding without though of performances leads to poor design habits that will bite. Many small inconsequential performance extras can quickly sum up to overall poor performance. Especially true in JavaScript where the target platform's capabilities vary so greatly. What is inconsequential on a top end desktop can be unusable on a $15 tablet.

Design

If you have followed the Code Review posting rules and your code is production code then it is very poor quality, as it is flat, in the global scope, and not reusable at all.

You should always write code inside a function. Even if its just an example writing functions will result in better design.

Style

  • The indenting is poor and obscuring the logic.
  • You have redundant code. Unneeded brackets () in headersMap.
  • Poor naming of the _.reduce callbacks arguments

Rewrite

Using your algorithm and rewriting into a named function your code is more readable, reusable, and cleaner (name space wise) as

function namedEvents(eventList) {
    const map = ({onClick, children}) => onClick ? {onClick} : _.map(children, map);
    const reduce = (result, child) => ({...result, [child]: null});
    return _.reduce(
        _.map(_.flatMap(eventList, map), 'onClick'),
       reduce, 
       {}
    );
}

Avoiding the needless re-creation of results for each entry you get

function namedEvents(eventList) {
    const map = ({onClick, children}) => onClick ? {onClick} : _.map(children, map);
    const reduce = (result, child) => (result[child] = null, result);
    return _.reduce(
        _.map(_.flatMap(eventList, map), 'onClick'),
       reduce, 
       {}
    );
}

Redesign

To avoid the CPU overhead the code can be written as a simple recursive function. The functions below are an order of magnitude quicker than your algorithm.

There is no need to load lodDash.js thus improving the page load time.

As you already use recursion I will assume that there are no cyclic references in the dataset.

Your code ignores children if onClick is truthy so will do the same

For the best re-usability the key "onClick" name can be passed as an argument.

function namedEvents(eventList, results = {}, key = "onClick") {
    for (const child of eventList) {
        if (child[key]) { results[child[key]] = null }
        else if (child.children) { namedEvents(child.children, results, key) }
    }
    return results;
}

or

function namedEvents(eventList, results = {}, key = "onClick") {
    for (const child of eventList) {
        if (child[key]) { 
            results[child[key]] = null;
        } else if (child.children) { 
            namedEvents(child.children, results, key);
        }
    }
    return results;
}

Or not having an adaptive key name

function namedEvents(eventList, results = {}) {
    for (const {onClick, children} of eventList) {
        if (onClick) { results[onClick] = null }
        else if (children) { namedEvents(children, results) }
    }
    return results;
}

Or

function namedEvents(eventList, results = {}) {
    return eventList.reduce((result, {onClick, children}) => 
            onClick? (results[onClick] = null, results) : 
                children ? namedEvents(children, results) : results,
        results
    );
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ So crisp and clear. Thanks @Blindman67 \$\endgroup\$ – a2441918 Oct 20 '19 at 23:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.