smaica
smaica

Reputation: 817

How to "transpose" Objects in TypeScript

Is there a way to nicely convert from an object:

Key_1 : {Head_1 : "val_11", Head_2 : "val_21", Head_3 : "val_31"}
Key_2 : {Head_1 : "val_12", Head_2 : "val_22", Head_3 : "val_32"}
Key_3 : {Head_1 : "val_13", Head_2 : "val_23", Head_3 : "val_33"}

to:

Head_1 : {Key_1 : "val_11", Key_2 : "val_12", Key_3: "val_13"}
Head_2 : {Key_1 : "val_21", Key_2 : "val_22", Key_3: "val_23"}
Head_3 : {Key_1 : "val_31", Key_2 : "val_32", Key_3: "val_33"}

In TypeScript?

Thanks a lot!

Upvotes: 0

Views: 1034

Answers (3)

Nitish Narang
Nitish Narang

Reputation: 4184

You can use "for..of" and "Object.entries" like below to transpose

BTW, it's always better to write your approach/what have you tried in OP so that fellow users see where you are heading towards

var obj = {
  Key_1 : {Head_1 : "val_11", Head_2 : "val_21", Head_3 : "val_31"},
  Key_2 : {Head_1 : "val_12", Head_2 : "val_22", Head_3 : "val_32"},
  Key_3 : {Head_1 : "val_13", Head_2 : "val_23", Head_3 : "val_33"}
}

function transpose(obj) {
  let newObj = {}
  for(let [key, value] of Object.entries(obj)) {
    for(let [k, v] of Object.entries(value)) { 
       newObj[k] = newObj[k] || {}
       newObj[k][key] = v
    }
  }
  return newObj
}

console.log(transpose(obj))

Upvotes: 3

jcalz
jcalz

Reputation: 329013

The other answers are great but I don't see anything that deals with the type system specific to TypeScript. The following type definitions and signature should allow the compiler to understand the type of the transposed object (assuming TS2.8+):

type AllKeys<T> = T extends any ? keyof T : never;
type RelevantKeys<T, K extends keyof T[keyof T]> = {
    [L in keyof T]: K extends keyof T[L] ? L : never
}[keyof T];

type Transpose<T> = {
    [K in AllKeys<T[keyof T]>]: {
        [L in RelevantKeys<T, K>]: T[L][K]
    }
};    

function transpose<T extends object & Record<keyof T, object>>(t: T): Transpose<T>;
// one possible implementation, use your favorite from above
function transpose(t: { [k: string]: { [l: string]: any } }) {
    const ret = {} as { [l: string]: { [k: string]: any } };
    Object.keys(t).forEach(k =>
        Object.keys(t[k]).forEach(l => {
            if (!(l in ret)) ret[l] = {};
            ret[l][k] = t[k][l];
        })
    );
    return ret;
}

Observe the types:

var obj = {
  Key_1: { Head_1: "val_11", Head_2: "val_21", Head_3: "val_31" },
  Key_2: { Head_1: "val_12", Head_2: "val_22", Head_3: "val_32" },
  Key_3: { Head_1: "val_13", Head_2: "val_23", Head_3: "val_33" }
}
const transposed = transpose(obj);

// the following are compile-time IntelliSense and/or error messages
transposed.Head_1; // {Key_1: string, Key_2: string, Key_3: string};
transposed.Haed_2; // error, property Haed2 does not exist
transposed.Head_3.Key_2; // string

const x = { a: { b: 1, c: true }, d: { e: "hey", f: undefined } };
const y = transpose(x);
y.e.d; // string
y.e.a; // error, property 'a' does not exist on {d: string}
y.b.a; // number
y.b.d; // error, property 'd' does not exist on {a: number}

Hope that helps!

Upvotes: 2

str
str

Reputation: 44979

Another approach would be to use Array.prototype.reduce:

const input = {
  Key_1: {Head_1: 'val_11', Head_2: 'val_21', Head_3: 'val_31'},
  Key_2: {Head_1: 'val_12', Head_2: 'val_22', Head_3: 'val_32'},
  Key_3: {Head_1: 'val_13', Head_2: 'val_23', Head_3: 'val_33'},
};

const output = Object.entries(input).reduce((acc, [key, keyVal]) => {
  Object.entries(keyVal).forEach(([head, headVal]) => {
    acc[head] = acc[head] || {};
    acc[head][key] = headVal;
  });
  return acc;
}, {});

console.log(output);

Don't forget to transpile and add polyfills if required for compatibility.

Upvotes: 0

Related Questions