Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 765
Unknown Avatar asked Nov 22 '25 20:11

Unknown


1 Answers

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.

like image 157
T.J. Crowder Avatar answered Nov 25 '25 08:11

T.J. Crowder



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!