8

I'm looking for an efficient way to return unique values in objects inside an array. For example the next object:

{
    "products": [{
        "id": 1, 
        "category": "test1",
        "tags": {
            "option": ["A", "B"]
        }
    }, {
        "id": 2,
        "category": "test2",
        "tags": {
            "option": ["B"],
            "type": ["A", "B", "C"]
        }
    }, {
        "id": 3,
        "category": "test1",
        "tags": {
            "type": ["A"]
        }
    }, {
        "id": 4,
        "category": "test2",
        "tags": {
            "option": ["B", "C"],
            "type": ["A", "C"]
        }
    }]
}

What I want to return is the following:

{"option": [ "A", "B", "C" ] },{"type": ["A", "B", "C"] }

So I want for each item inside the tags object a new object. After that, I want an array with all unique values over all the products.

I do somewhat the same with another function:

Array.from(new Set(data.map(p => { return p.category; })))

This is a level higher which makes it easier. Can someone push me in the right direction?

4
  • note that a JSON is a string representing a JS object. What you currently have is simply an object Commented Mar 4, 2020 at 8:47
  • What is p.category? There is no mention of it in your sample data. Commented Mar 4, 2020 at 8:48
  • ... Which happens to be vaild JSON. If it's contained in a data.json file, it's not an object. Commented Mar 4, 2020 at 8:48
  • I added the category, sorry wasn't present in the code. The JSON is coming back from an API, this is my first post on stack overflow.. I think that's the issue with the JS object @Cid Commented Mar 4, 2020 at 8:53

5 Answers 5

7

Make two sets instead, one for the options found so far, and one for the types found so far:

const obj = {
  "products": [{
    "id": 1,
    "tags": {
      "option": ["A", "B"]
    }
  }, {
    "id": 2,
    "tags": {
      "option": ["B"],
      "type": ["A", "B", "C"]
    }
  }, {
    "id": 3,
    "tags": {
      "type": ["A"]
    }
  }, {
    "id": 4,
    "tags": {
      "option": ["B", "C"],
      "type": ["A", "C"]
    }
  }]
};
const options = new Set();
const types = new Set();
for (const { tags: { option=[], type=[] } } of obj.products) {
  for (const o of option) options.add(o);
  for (const t of type) types.add(t);
}
console.log({
  option: [...options],
  type: [...types]
});

An alternative, for arbitrary keys:

const obj = {
  "products": [{
    "id": 1,
    "tags": {
      "option": ["A", "B"]
    }
  }, {
    "id": 2,
    "tags": {
      "option": ["B"],
      "type": ["A", "B", "C"]
    }
  }, {
    "id": 3,
    "tags": {
      "type": ["A"]
    }
  }, {
    "id": 4,
    "tags": {
      "option": ["B", "C"],
      "type": ["A", "C"]
    }
  }]
};
const setObj = {};
for (const { tags } of obj.products) {
  for (const [key, arr] of Object.entries(tags)) {
    if (!setObj[key]) setObj[key] = new Set();
    for (const item of arr) setObj[key].add(item);
  }
}
const output = Object.fromEntries(
  Object.entries(setObj).map(([key, set]) => [key, [...set]])
);
console.log(output);

Sign up to request clarification or add additional context in comments.

2 Comments

just about to answer . You posted first .
Thanks! I marked this one as best cause it seems to fit best my needs.
4

You could take a Map for wanted keys and collect the values in a Set.

function getUnique(array, keys) {
    var maps = new Map(keys.map(k => [k, new Set]));

    array.forEach(({ tags }) =>
        keys.forEach(k => (tags[k] || []).forEach(v => maps.get(k).add(v))));

    return Array.from(maps, ([k, s]) => ({ [k]: Array.from(s) }));
}

var data = { products: [{ id: 1, tags: { option: ["A", "B"] } }, { id: 2, tags: { option: ["B"], type: ["A", "B", "C"] } }, { id: 3, tags: { type: ["A"] } }, { id: 4, tags: { option: ["B", "C"], type: ["A", "C"] } }] },
    unique = getUnique(data.products, ['option', 'type']);

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

Comments

3

You can create an object with two required keys and then set then to empty arrays. Then loop through the array and add the new elements to that array and after that remove the duplicates

const arr = [{
"id": 1,
"tags": {
  "option": ["A", "B"]
}}, {
"id": 2,
"tags": {
  "option": ["B"],
  "type": ["A", "B", "C"]
} }, {
"id": 3,
"tags": {
  "type": ["A"]
}}, {
"id": 4,
"tags": {
  "option": ["B", "C"],
  "type": ["A", "C"]
}}]

const object = {option:[] , type: []}
arr.forEach(({tags}) => {
  for(let prop in object){
    if(tags[prop]){
      object[prop] = [...new Set([...object[prop], ...tags[prop]])]
    }
  }
  
})
console.log(object)

Comments

3

You can use Array.prototype.reduce() to make the result variable { option: [], type: [] }

Code:

const data = {"products": [{"id": 1,"tags": {"option": ["A", "B"]}}, {"id": 2,"tags": {"option": ["B"],"type": ["A", "B", "C"]}}, {"id": 3,"tags": {"type": ["A"]}}, {"id": 4,"tags": {"option": ["B", "C"],"type": ["A", "C"]}}]}

const result = data.products.reduce((a, { tags }) => {
  ['option', 'type'].forEach(prop => {
    a[prop] = [...new Set(a[prop].concat(tags[prop] || []))]
  })
  return a
}, { option: [], type: [] })

console.log(result)

Comments

3

A simple yet efficient solution is:

const optionSet = new Set();
const typeSet = new Set();

data.products.forEach( pr =>{
    if(pr.tags.option){
      pr.tags.option.forEach( op =>{
        optionSet.add(op)
      })
    }
    if(pr.tags.type){
      pr.tags.type.forEach( tp =>{
        typeSet.add(tp);
      })
    }
})

Performance comparison:

const obj = {
  "products": [{
    "id": 1,
    "tags": {
      "option": ["A", "B"]
    }
  }, {
    "id": 2,
    "tags": {
      "option": ["B"],
      "type": ["A", "B", "C"]
    }
  }, {
    "id": 3,
    "tags": {
      "type": ["A"]
    }
  }, {
    "id": 4,
    "tags": {
      "option": ["B", "C"],
      "type": ["A", "C"]
    }
  }]
};

//efficient solution
let t0 = performance.now();
const optionSet = new Set();
const typeSet = new Set();

obj.products.forEach( pr =>{
    if(pr.tags.option){
      pr.tags.option.forEach( op =>{
        optionSet.add(op)
      })
    }
    if(pr.tags.type){
      pr.tags.type.forEach( tp =>{
        typeSet.add(tp);
      })
    }
})
let s1Result = { 
  options: [...optionSet],
  types: [...typeSet]
}
let t1 = performance.now();
let s1Runtime = t1-t0
console.log("efficient took: ",s1Runtime);


//accepted answer
let t2 = performance.now();
const setObj = {};
for (const { tags } of obj.products) {
  for (const [key, arr] of Object.entries(tags)) {
    if (!setObj[key]) setObj[key] = new Set();
    for (const item of arr) setObj[key].add(item);
  }
}
const s2Result = Object.fromEntries(
  Object.entries(setObj).map(([key, set]) => [key, [...set]])
);
let t3 = performance.now();
let s2Runtime = t3-t2
console.log("current solution took: ",s2Runtime);


//runtime comparison
console.log("efficient solution is "+ ((s1Runtime)/(s2Runtime)).toFixed(2)*100 + " % faster current solution");

console.log("efficient solution result:", s1Result);
console.log("current solution result:", s2Result);

1 Comment

@Frank I'd added a performance comparison for your reference;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.