admin管理员组

文章数量:1221774

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?

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?

Share Improve this question edited Mar 4, 2020 at 8:52 Fred 3,4214 gold badges37 silver badges58 bronze badges asked Mar 4, 2020 at 8:43 FrankFrank 12310 bronze badges 4
  • note that a JSON is a string representing a JS object. What you currently have is simply an object – Cid Commented Mar 4, 2020 at 8:47
  • What is p.category? There is no mention of it in your sample data. – Terry 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. – Cerbrus 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 – Frank Commented Mar 4, 2020 at 8:53
Add a comment  | 

5 Answers 5

Reset to default 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);

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; }

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)

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

  • then iterate over desired properties ['option', 'type'] with Array.prototype.forEach()
  • finally get the unique values array with the Set object after Array.prototype.concat() the arrays for each products

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)

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);

本文标签: javascriptLoop over objects inside a list and return unique valuesStack Overflow