Unknown
Unknown

Reputation: 919

Create object from array of objects by dynamic properties

I have an array of objects

  "options": [
    {
      "width": 10,
      "height": 20
    },
    {
      "width": 20,
      "height": 40
    },
    {
      "width": 30,
      "height": 60
    }
  ]

That I want convert to the following

{ width: [10, 20, 30], height: [20, 40, 60] }

Now keep in mind that the keys are dynamic. In this instance it's width and height.

I do actually have solution.

const pluck = (arr: Record<string, any> | undefined, property: string) => {
  return arr?.map((obj: any) => {
    return obj[property];
  });
};
const unique = (arr: any[] | undefined) =>
  arr ? Object.keys(Object.assign({}, ...arr)) : null;

const keys = unique(myArray);
const options = keys
  ? Object.fromEntries(keys.map((key) => [key, pluck(myArray, key)]))
  : null;

But can I make this shorter?

Upvotes: 3

Views: 1708

Answers (4)

T.J. Crowder
T.J. Crowder

Reputation: 1075905

The simplest way is just to write a loop. Let's start with just JavaScript and then we'll come back to types:

const result = {};
for (const entry of myArray) {
    for (const key in entry) { // or: of Object.keys(entry)
        const values = result[key] = result[key] ?? [];
        values.push(entry[key]);
    }
}

Live Example:

const myArray = [
    {
        "width": 10,
        "height": 20
    },
    {
        "width": 20,
        "height": 40
    },
    {
        "width": 30,
        "height": 60
    }
];

const result = {};
for (const entry of myArray) {
    for (const key in entry) { // or: of Object.keys(entry)
        const values = result[key] = result[key] ?? [];
        values.push(entry[key]);
    }
}
console.log(result);
.as-console-wrapper {
    max-height: 100% !important;
}

Re for (const key in entry) vs. for (const key of Object.keys(entry), the former also visits inherited properties. Your example doesn't have any inherited properties, but if you had them, you could either add a hasOwnProperty check, or if you don't mind the temporary array, use of Object.keys(entry).

Okay, so: Types. I was expecting this to be quite difficult, but it turned out not to be, which makes me suspicious I'm missing something. :-) We'd use a function for it so we can use type parameters:

function condense<ObjType extends object>(array: ObjType[]) {
    const result: Record<string, any> = {};
    for (const entry of array) {
        for (const key in entry) { // or: of Object.keys(entry)
            const values = result[key] = result[key] ?? [];
            values.push(entry[key]);
        }
    }
    return result as {[key in keyof ObjType]: ObjType[key][]};
}
const result = condense(myArray);
console.log(result);

Playground link

Since the result is built up over time, I used fairly loose types within the condense function, but the input and output are properly typed. For instance, the result of result above is { width: number[]; height: number[]; }, which is what we want. It even works if we throw in a third property (say, description) of a different type (say, string), giving us { width: number[]; height: number[]; description: string[]; }: Playground link


The fad at present is to use reduce, but there's no reason to use it here, all it does is add complexity.

Upvotes: 3

Hanna Rose
Hanna Rose

Reputation: 422

i do with for loop, because its more responsive and faster than map or reduce.

  const options = [
    { "width": 10, "height": 20 },
    { "width": 20, "height": 40 },
    { "width": 30, "height": 60 }
  ];
//That I want convert to the following
//{ width: [10, 20, 30], height: [20, 40, 60] }

let newobj = new Object();
for( let a of options ){
  for( let b of Object.keys(a) ){
    if( !newobj[b] ) newobj[b]=[]; newobj[b].push(a[b]);
  }
}
console.log(newobj)

Upvotes: 0

Rahmatullah Darwish
Rahmatullah Darwish

Reputation: 5

var height = [], width = [];
options.forEach((op) =>{
  height.push(op.height)
  width.push(op.width)
})
   var returnValue = {width: width, height: height}

Upvotes: -1

Arman Charan
Arman Charan

Reputation: 5797

See Array.prototype.reduce() for additional info.

const input = [
  { width: 100, height: 100 },
  { width: 200, height: 200 },
  { width: 300, height: 300 },
];

const group = (someArrayOfObjects) => {
  return someArrayOfObjects.reduce((acc, current) => {
    Object.entries(current).forEach(([key, value]) => {
      if (!acc[key]) {
        acc[key] = [];
      }
      acc[key].push(value);
    });
    return acc;
  }, {});
};

console.log(group(input))

Upvotes: -1

Related Questions