-2

I have a nested object look like this:

let obj = { 
    F:{
        asian: {
            "35-44": 1,
            "55-": 1,
        },
        "asian/black": {
            "0-24": 1,
            "35-44": 1,
            "45-54": 2,
        },
    },
    M:{
        asian: {
            "35-44": 1,
            "55-": 1,
        },
        white: {
            "0-24": 1,
            "35-44": 1,
            "45-54": 2,
        },
    },

}

And I want to flatten the object to this:

res = {
    F: 6,
    M: 6,
    asian: 4,
    "asian/black": 4,
    white: 4, 
    "0-24": 2,
    "35-44": 4,
    "45-54": 4,
    "55-": 2,
}

That every value in res should be the sum of the deepest object values(F, M) and object values with the same key(0-24, 35-44...). I feel this can be done using recursion and just can't get it right. The code I write:

let returnVal = 0
const flatten = (obj, prefix = '', res = {}) => {

  return Object.entries(obj).reduce((r, [key, val]) => {
    if(typeof val === 'object'){ 
      flatten(val, key, r)
    } else {
      res[key] = val
      returnVal = val;
    }

    if (key in res) {
        res[key] += returnVal
    } else {
        res[key] = 0
        res[key] += returnVal
    }
    
    return r
  }, res)
}
console.log(flatten(obj))

it will output:

result = {
"0-24": 2,
"35-44": 2,
"45-54": 4,
"55-": 2,
F: 2,
M: 2,
asian: 2,
"asian/black": 2,
white: 2,
}

F, M, and some other keys are not correct. Thanks!

Albert Hsu
  • 13
  • 2

2 Answers2

1

Another, perhaps simpler, approach is as follows:

const consolidate = (obj, path = [], results = {}) =>
  Object .entries (obj) .reduce ((results, [k, v]) => 
    Object (v) === v 
      ? consolidate (v, [...path, k], results)
      : [...path, k] .reduce (
          (results, n) => ({...results, [n] : (results[n] || 0) + v}), 
          results
        ),
  results)

const data = {F: {asian: {"35-44": 1, "55-": 1}, "asian/black": {"0-24": 1, "35-44": 1, "45-54": 2}}, M: {asian: {"35-44": 1, "55-": 1}, white: {"0-24": 1, "35-44": 1, "45-54": 2}}}

console .log (consolidate (data))
.as-console-wrapper {min-height: 100% !important; top: 0}

We recursively track paths taken through the object, such as ['F', 'asian/black', '45-54'] or ['M', 'white'] or simply ['f'] as well as an object containing the final results. When we the value at the current node is an object, we recur, adding the current property name to the path. When it's not (for this data it must therefore hit a number), we hit a base case in which we take each node in the current path, and update the results object by adding that number to the value for the node in the results object, or setting it to the current value if that value doesn't exist.

There is a potential issue with the default parameters, as described in another Q & A. If someone tried to map the consolidate function directly over an array of input objects, it would fail. If this is a concern, it's easy enough to swap the default parameters for a wrapper function:

const _consolidate = (obj, path, results) =>
  Object .entries (obj) .reduce ((results, [k, v]) => 
    Object (v) === v 
      ? _consolidate (v, [...path, k], results)
      : [...path, k] .reduce (
          (results, n) => ({...results, [n] : (results[n] || 0) + v}), 
          results
        ),
  results)

const consolidate = (obj) => 
  _consolidate (obj, [], {})

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
0

const data = {
  F: {
    asian: {
      "35-44": 1,
      "55-": 1,
    },
    "asian/black": {
      "0-24": 1,
      "35-44": 1,
      "45-54": 2,
    },
  },
  M: {
    asian: {
      "35-44": 1,
      "55-": 1,
    },
    white: {
      "0-24": 1,
      "35-44": 1,
      "45-54": 2,
    },
  },
};

const isObject = obj => Object.prototype.toString.call(obj) === "[object Object]";

function nestKeys(obj, parent = "") {
  return Object.keys(obj).map(key => {
    const k = parent.length ? [parent, key].join(".") : key;

    if (!isObject(obj[key])) {
      return k;
    }

    return nestKeys(obj[key], k);
  }).flat();
}

function flatObj(obj) {
  const map = {};
  const keys = nestKeys(obj);

  keys.forEach(nestedKey => {
    const splited = nestedKey.split(".");

    const val = splited.reduce((acc, cur) => acc[cur], obj);

    splited.forEach(k => {
      map[k] = (map[k] || 0) + val;
    })
  });

  return map;
}


console.log(flatObj(data));
Kharel
  • 819
  • 8
  • 16