2

I want to create an array of objects from an array of objects, this is the data:

// Input:
const shippingMethodOptions = [{
  id: 1,
  carrier_token: 'fedex',
  carrier_name: 'Fedex',
  weight: 1,
  price: 3
}, {
  id: 2,
  carrier_token: 'fedex',
  carrier_name: 'Fedex',
  weight: 2,
  price: 6
}, {
  id: 3,
  carrier_token: 'fedex',
  carrier_name: 'Fedex',
  weight: 6,
  price: 9
}, {
  id: 4,
  carrier_token: 'usps',
  carrier_name: 'Usps',
  weight: 6,
  price: 9
}, {
  id: 5,
  carrier_token: 'delaware',
  carrier_name: 'Delaware',
  weight: 5,
  price: 10
}]

As you can see the data has carrier_token and carrier_name, what I want is to create an output array with the unique carriers like so:

// Output:
const carriers = [{
  carrier_token: 'fedex',
  carrier_name: 'Fedex'
}, {
  carrier_token: 'usps',
  carrier_name: 'USPS'
}, {
  carrier_token: 'delaware',
  carrier_name: 'Delaware'
}]

What I have tried is building it with a for loop, however I would love to know if this is possible in a more clean way (reduce?)

This is my current solution so far (but again, I would love to know if it's possible to make it cleaner with Reduce etc.)

const getCarriers = options => {
  const carriers = []

  options.forEach(({ carrier_token, carrier_name }) => {
    const foundIndex = _.findIndex(carriers, carrier => carrier.carrier_token === carrier_token)

    if (foundIndex !== -1) {
      return
    }

    carriers.push({
      carrier_token,
      carrier_name
    })
  })

  return carriers
}

const carriers = getCarriers(shippingMethodOptions)
7
  • @AndrewLi Sorry for that, I tried it with a for loop, but I want to know if it's possible using reduce / map etc. Commented Jul 11, 2017 at 3:48
  • Could you include your attempt in the post? Commented Jul 11, 2017 at 3:48
  • @AndrewLi Yep, done Commented Jul 11, 2017 at 3:54
  • @AndrewLi I don't think it's very fair to downvote an answer when it correctly answers the question. Based on your comments on my answer, even if my answer had been correct you would have downvoted. I also do explain my code very thoroughly, but I start with a code dump on simple questions like this and then edit the answer so that I have a chance of actually being seen before everyone else code-dumps and gets upvoted and/or accepted. Commented Jul 11, 2017 at 3:55
  • @Clonkex I don't think it's fair to encourage bad behavior on SO. Since the OP hadn't shown their effort then, it could have sent the message to other posters that asking for someone to write code for them was okay. Had your answer been correct and had explanation off the bat, I would have upvoted. Commented Jul 11, 2017 at 3:58

2 Answers 2

6

Seems like a pretty straight-forward reduce job.

What you'll need is a way to keep track of existing carriers. I recommend using a Set and storing keys made from the token and name, for example in a string like "<token>:<name>".

const shippingMethodOptions = [{"id":1,"carrier_token":"fedex","carrier_name":"Fedex","weight":1,"price":3},{"id":2,"carrier_token":"fedex","carrier_name":"Fedex","weight":2,"price":6},{"id":3,"carrier_token":"fedex","carrier_name":"Fedex","weight":6,"price":9},{"id":4,"carrier_token":"usps","carrier_name":"Usps","weight":6,"price":9},{"id":5,"carrier_token":"delaware","carrier_name":"Delaware","weight":5,"price":10}]

const carriers = shippingMethodOptions.reduce(function(arr, smo) {
  let key = `${smo.carrier_token}:${smo.carrier_name}`
  return this.has(key) ? arr : this.add(key) && arr.concat({
    carrier_token: smo.carrier_token,
    carrier_name: smo.carrier_name
  });
}.bind(new Set()), [])

console.info(carriers)

I'm binding the Set to the reduce callback so it can be used via the this keyword however you could just as easily create it outside the callback scope and store it in a variable, eg

const keys = new Set()
Sign up to request clarification or add additional context in comments.

15 Comments

I had a pretty similar answer going. +1
Damn. I think I'm a good JS programmer until I see this and realise I have zero idea what's going on. This kind of coding is so hard to read IMO.
@AndrewLi I do have some idea how reduce works since I wrote my own to figure it out (jsfiddle.net/9618or70/1), but I don't understand how short circuiting applies here and I thought bind was specific to jQuery... looks like I have some painful learning to get through :/
@Clonkex for reference ~ Function.prototype.bind
|
3

I'd consider using .reduce() with a Map(), where the map entries have keys based on ${carrier_token}:${carrier_name} and values in the required object format. Then use Array.from() on the .values() of the map.

const shippingMethodOptions = [{"id":1,"carrier_token":"fedex","carrier_name":"Fedex","weight":1,"price":3},{"id":2,"carrier_token":"fedex","carrier_name":"Fedex","weight":2,"price":6},{"id":3,"carrier_token":"fedex","carrier_name":"Fedex","weight":6,"price":9},{"id":4,"carrier_token":"usps","carrier_name":"Usps","weight":6,"price":9},{"id":5,"carrier_token":"delaware","carrier_name":"Delaware","weight":5,"price":10}]

const carriers = Array.from(
  shippingMethodOptions.reduce((map, {carrier_token, carrier_name}) => {
    const key = `${carrier_token}:${carrier_name}`
    if (!map.has(key)) map.set(key, { carrier_token, carrier_name })
    return map
  }, new Map()).values()
)

console.log(carriers)

Or if just the carrier_token is enough for uniqueness you can simplify it:

const shippingMethodOptions = [{"id":1,"carrier_token":"fedex","carrier_name":"Fedex","weight":1,"price":3},{"id":2,"carrier_token":"fedex","carrier_name":"Fedex","weight":2,"price":6},{"id":3,"carrier_token":"fedex","carrier_name":"Fedex","weight":6,"price":9},{"id":4,"carrier_token":"usps","carrier_name":"Usps","weight":6,"price":9},{"id":5,"carrier_token":"delaware","carrier_name":"Delaware","weight":5,"price":10}]

const carriers = Array.from(
  shippingMethodOptions.reduce((map, {carrier_token, carrier_name}) => {
    if (!map.has(carrier_token)) map.set(carrier_token, { carrier_token, carrier_name })
    return map
  }, new Map()).values()
)

console.log(carriers)

4 Comments

Be careful, you'll match "fede" + "xFedex", etc
You could shorten your callback to return map.has(key) ? map : map.set(key, obj) as set returns the map
@Phil - Another good point, thanks. I find the if a little easier to read because then I don't have to remember what .set() returns. But on the other hand, using your suggestion means the curly brackets and return keyword can be dropped from the arrow function, at least from my second version that didn't have the key variable.
You don't actually need reduce here, just do new Map(data.map(x => [x.token, {x.token, x.name}]

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.