Stefan Paunovic
Stefan Paunovic

Reputation: 95

How to get an array of unique properties from an array of complex objects

I have an array of objects similar to

const array = [
{
    documentId: "document1",
    writers: [
        {
            name: "writer1", 
            date: "2017-12-11"
        },
        {
            name: "writer2", 
            date: "2017-12-11"
        }
    ]
},
{
    documentId: "document2",
    writers: [
        {
            name: "writer1", 
            date: "2017-12-11"
        },
        {
            name: "writer3", 
            date: "2017-12-11"
        }
    ]
},
{
    documentId: "document3",
    writers: [
        {
            name: "writer3", 
            date: "2017-12-11"
        },
        {
            name: "writer4", 
            date: "2017-12-11"
        }
    ]
},

I'm trying to extract all the unique writer's names and match them to all the documents they have written so that the final array looks something like this:

const finalArray = [
    {name: "writter1", documents: ["document1", "document2"]},
    {name: "writter2", documents: ["document1"]},
    {name: "writter3", documents: ["document2", "document3"]},
    {name: "writter4", documents: ["document3"]}
]

Upvotes: 1

Views: 65

Answers (3)

Nina Scholz
Nina Scholz

Reputation: 386560

You could take a Map which takes all names and the associated documents. If no map is avaliable for a name, then create a new entry.

A then end, get all objects of the map.

var array = [{ documentId: "document1", writers: [{ name: "writer1", date: "2017-12-11" }, { name: "writer2", date: "2017-12-11" }] }, { documentId: "document2", writers: [{ name: "writer1", date: "2017-12-11" }, { name: "writer3", date: "2017-12-11" }] }, { documentId: "document3", writers: [{ name: "writer3", date: "2017-12-11" }, { name: "writer4", date: "2017-12-11" }] }],
    map = new Map,
    result;

array.forEach(function (o) {
    o.writers.forEach(function ({ name }) {
        map.has(name) || map.set(name, { name, documents: [] });
        map.get(name).documents.push(o.documentId);
    });
});

result = Array.from(map.values());

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

dashton
dashton

Reputation: 2704

You can use Map to keep writers as keys then call reduce on your array to map documents to writers, then call values() on the Map object:

const ret = Array.from(array.reduce((acc, val) => {
  val.writers.forEach(({ name }) => {
    let w = acc.get(name) || { name: name, documents: [] };
    w.documents = w.documents.concat(val.documentId);
    acc.set(name, w);
  });
  return acc;
}, new Map()).values());

console.log(ret);

Upvotes: 2

radar155
radar155

Reputation: 2220

Maybe there will be a more elegant map-reduce solution. Anyway, this will works even if it's ugly:

const array = //your data
var output = []
for (let i = 0; i < array.length; i++)
    for (let k = 0; k < array[i].writers.length; k++) {
        let t = output.find(function(a){return array[i].writers[k].name === a.name})
        if (t)
            t.documents.push(array[i].documentId)
        else
            output.push({name: array[i].writers[k].name, documents: [array[i].documentId]})
  }
console.log(output)

https://jsfiddle.net/50oph5g4/1/

Upvotes: 1

Related Questions