JS Array Manipulation - Extract key,value pairs

need to manipulate an array in order to use it with a graphic that accepts a specific shape of data.

Here is the current shape

[
{
Name:"Alfred",
French:15,
English:13,
Spanish:9
},

{
Name:"Robert",
French:5,
English:43,
Spanish:16
}
,

{
Name:"Jonathon",
French:15,
English:32,
Spanish:18
}
]

I Need to transform this array into this shape:

[
{
subject:"French",
Alfred:15,
Robert:5,
Jonathon:15
},

{
subject:"English",
Alfred:13,
Robert:43,
Jonathon:32
},

{
subject:"Spanish",
Alfred:9,
Robert:16,
Jonathon:18
}


]

My problem is that I can’t extract the label of the subject once, and then fill the object. Thank you for your help!

I don’t think this is the best solution, being O(n*m), but it’s what I came up with on the fly. It also involves two passes - I’m sure there is a way to do it in one but it’s not coming to me. They can no doubt be combined, but this might be more readable. It is unclear how large this list is expected to be.

const listByName = [
  {
    Name: "Alfred",
    French: 15,
    English: 13,
    Spanish: 9
  },

  {
    Name: "Robert",
    French: 5,
    English: 43,
    Spanish: 16
  },
  {
    Name: "Jonathon",
    French: 15,
    English: 32,
    Spanish: 18
  }
];

const listBySubject = [];

// make empty list
listByName.forEach(nameRecord => {
  const keys = Object.keys(nameRecord);
  const subjects = keys.filter(el => el !== 'Name');
  subjects.forEach(subject => {
    if (listBySubject.findIndex(el => el.subject === subject) === -1) {
      listBySubject.push({ subject });
    }
  });
});

// fill the list
listByName.forEach(nameRecord => {
  const keys = Object.keys(nameRecord);
  const subjects = keys.filter(el => el !== 'Name');
  const name = nameRecord.Name;
  subjects.forEach(subject => {
    const index = listBySubject.findIndex(el => el.subject === subject);
    listBySubject[index] = { ...listBySubject[index], [name]: nameRecord[subject] };
  });
});

console.log('by subject', listBySubject);

I have a pen here.

1 Like

It could be made more efficient with more information. Will the subjects always be those 3? Will some students sometimes have some subjects that others don’t? I built this on the worst case - where we can make none of those assumptions.

1 Like

Hi Kevin,

Thank you for your answer. The list can contain many entries, in some cases, there will be keys that are not available for every entry. In this case, it is allowed to put 0 ( The actual use case is not about students and their grades, I chose this analogy for simplicity ends.).

If you must have an entry for every possible subject, then this would need to be modified a little. You could pass through and generate all possible subjects (if you don’t already know what they are) and create a template for each object in the final array.

1 Like

Interesting problem, I gave it a shot. The first step:

const resultObj = arr.reduce((acc,obj) => {
    const [[k,name], ...subjects] = Object.entries(obj);

    subjects.forEach(s => {
        const [subject, grade] = s;
        if (!acc.hasOwnProperty(subject)){
            acc[subject] = {};
        }
        acc[subject][name] = grade;
    });

    return acc
}, {})

It returns an object formed like:

{
    English:{
        Alfred:13,
        Robert:43,
        Jonathon:32
    },
    French:{
        ...
    }
}

Turning it into an array:

const resultArr = Object.entries(resultObj)
  .map(entry => ({Subject:entry[0], ...entry[1]}));

Because it needs that second loop, I guess this is hardly more perfomant than @kevinSmith’s solution though.

1 Like