skip to Main Content

I have fairly symmetrical data I want to reduce:

const data = [
  {
    name: 'Bob',
    relations: {
      siblings: [
        {
          name: 'Tom',
          age: '20'
          snacks: [
            {
              type: 'yogurt'
            }
          ]
        },
        {
          name: 'Jacob'
          snacks: [
            {
              type: 'cookie',
              amount: '2'
            }
            {
              type: 'brownie',
              amount: '3'
            }
          ]
        }
      ]
    }
  },
  {
    name: 'Robert',
    relations: {
      siblings: [
        {
          name: 'Timmy',
          age: '16'
          snacks: [
            {
              type: 'fudge'
            }
            {
              type: 'brownie'
            }
          ]
        }
      ]
    }
  }
];

What I’m trying to produce:

const output = {
  name: ['Bob', 'Robert'],
  relations: {
    siblings: {
      name: ['Tom', 'Jacob', 'Timmy'],
      age: ['20', '16'],
      snacks: {
        type: ['yogurt', 'cookie', 'fudge', 'brownie'],
        amount: ['2', '3']
      }
    }
  }
}

I understand how to do this without recursion, but I wanted a solution that would go deep.
Usually I just use reduce with recursion, but then I realized that I would have to add the value to the current level of the object which I don’t know how to do.

const compact = (value) => {
  if (typeof value === 'string') {
    return { [value]: '' }; // turning into an object for the mean time
  }

  if (Array.isArray(object)) { }

  // if object
  return Object.entries(object).reduce((accum, [key, value]) => {
    // Do I have to pass accum[key] into compact here? Is this the correct way to do this?
    accum[key] = compact(value);
   
    return accum;
  }, {});
};

2

Answers


  1. By having only fixed properties like relations.siblings, you could take a dynamic approach and hand over only the target for getting a flat structure.

    const
        data = [{ name: 'Bob', relations: { siblings: [{ name: 'Tom', age: '20' }, { name: 'Jacob' }] } }, { name: 'Robert', relations: { siblings: [{ name: 'Timmy', age: '16' }] } }],
        getData = (array, target = {}) => {
            for (const { relations, ...rest } of array) {
                Object
                    .entries(rest)
                    .forEach(([k, v]) => (target[k] ??= []).push(v));
    
                if (relations)
                    getData(relations.siblings, (target.relations ??= {}).siblings ??= {});
            }
            return target;
        },
        result = getData(data);
        
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }
    Login or Signup to reply.
  2. Here is what I would propose: divide functionality into two different functions; one for summerizing a given object, and another for merging two objects that are already summerized.

    Then it could be like this:

    function merge(a, b) {
        const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])];
        return Object.fromEntries(keys.map(key => [key,
                  !Object.hasOwn(a, key) ? b[key]
                : !Object.hasOwn(b, key) ? a[key]
                : Array.isArray(a[key])  ? [...new Set([...a[key], ...b[key]])]
                : merge(a[key], b[key])
        ]));
    }
    
    function summerize(obj) {
        return  Object(obj) !== obj ? [obj] // Primitive is wrapped in array
              : Array.isArray(obj)  ? obj.reduce((acc, item) => merge(acc, summerize(item)), {})
              : Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, summerize(value)]))
    }
    
    // Demo on the input in the question:
    const data = [{name: 'Bob',relations: {siblings: [{name: 'Tom',age: '20'},{name: 'Jacob'}]}},{name: 'Robert',relations: {siblings: [{name: 'Timmy',age: '16'}]}}];
    const result = summerize(data);
    console.log(result);
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search