1

I'm trying to reduce the following data from video info into a custom Object structure that removes "label" keys duplicates but also keeps true all the encountered features that are available.

The initial data looks like this:

[
  {
    "label": "4320p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "2160p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "2160p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "1440p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "1440p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": true,
      "60fps": false
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": true,
      "60fps": false
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": true,
      "60fps": false
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": true,
      "60fps": false
    }
  }
]

As you can see there are many Objects for a single resolution label but some of them have HDR while others have 60fps while other might have none of them or just both.

What I'm trying to do is to reduce this array with the following reduce function. Assuming that resolutions is the above Object:

resolutions.reduce((unique, o) => {
    if (!unique.some((obj) => obj.label === o.label)) {
      unique.push(o);
    }
    return unique;
  }, []);

That gives me the following structure:

[
  {
    "label": "4320p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "2160p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "1440p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  }
]

But if you look closer you'll see that during the reduce operation some booleans are merged with the first element value for each unique label key and I need to have in the end the true feature enabled even if just one of them was true. The 2160p label is a good example where there actually is an object that has HDR: true but in the end I just get it as false

Do you have any idea on how to manage this situation with modern JavaScript?

2 Answers 2

1

It's easier if you first grouped by label using the .reduce and in each iteration:

  • If label is not in acc yet, add the first object (current item)
  • Else, update the features of the stored category in a way that if one of its feature is not available and the new current item in this category has it, set it to true

In the end, return the grouped list of objects with merged features:

const resolutions = [
  { "label": "4320p", "features": { "HDR": false, "60fps": true } },
  { "label": "2160p", "features": { "HDR": false, "60fps": true } },
  { "label": "2160p", "features": { "HDR": true, "60fps": true } },
  { "label": "1440p", "features": { "HDR": false, "60fps": true } },
  { "label": "1440p", "features": { "HDR": true, "60fps": true } },
  { "label": "1080p", "features": { "HDR": false, "60fps": true } },
  { "label": "1080p", "features": { "HDR": true, "60fps": true } },
  { "label": "1080p", "features": { "HDR": false, "60fps": true } },
  { "label": "1080p", "features": { "HDR": true, "60fps": true } },
  { "label": "720p", "features": { "HDR": false, "60fps": true } },
  { "label": "720p", "features": { "HDR": true, "60fps": true } },
  { "label": "720p", "features": { "HDR": false, "60fps": true } },
  { "label": "720p", "features": { "HDR": false, "60fps": false } },
  { "label": "720p", "features": { "HDR": true, "60fps": true } },
  { "label": "720p", "features": { "HDR": false, "60fps": false } },
  { "label": "480p", "features": { "HDR": true, "60fps": true } },
  { "label": "480p", "features": { "HDR": false, "60fps": false } },
  { "label": "480p", "features": { "HDR": false, "60fps": false } },
  { "label": "480p", "features": { "HDR": true, "60fps": false } },
  { "label": "360p", "features": { "HDR": true, "60fps": true } },
  { "label": "360p", "features": { "HDR": false, "60fps": false } },
  { "label": "360p", "features": { "HDR": false, "60fps": false } },
  { "label": "360p", "features": { "HDR": true, "60fps": false } },
  { "label": "240p", "features": { "HDR": true, "60fps": true } },
  { "label": "240p", "features": { "HDR": false, "60fps": false } },
  { "label": "240p", "features": { "HDR": false, "60fps": false } },
  { "label": "240p", "features": { "HDR": true, "60fps": false } },
  { "label": "144p", "features": { "HDR": true, "60fps": true } },
  { "label": "144p", "features": { "HDR": false, "60fps": false } },
  { "label": "144p", "features": { "HDR": false, "60fps": false } },
  { "label": "144p", "features": { "HDR": true, "60fps": false } }
];

// update category's features if any is not available yet, and the new object has it
const _getUpdatedFeatures = (currentFeatures={}, newFeatures={}) => {
  const updatedFeatures = {...currentFeatures};
  for (let [feature, currentlyAvailable] of Object.entries(currentFeatures)) {
    if(!currentlyAvailable && newFeatures[feature]===true) {
      updatedFeatures[feature] = true;
    }
  }
  return updatedFeatures;
}

// group by label and merge features availability
const res = Object.values(resolutions.reduce((acc, item) => {
  const { label } = item;
  const prev = acc[label];
  if(!prev) 
    acc[label] = item;
  else 
    acc[label] = { ...prev, features: _getUpdatedFeatures(prev.features, item.features) };
  return acc;
}, {}));

console.log(res);

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

3 Comments

Interesting solution and it works, just wondering if there is a more concise way to do it within the reduce() function.
Can you clarify more on what "concise" means here? The idea of updating the features can be done in any type of loop, but I did it like this for the sake of clarity
Just without the separated _getUpdatedFeatures() function, I've been reading the Docs for the reduce() function here but there are some concepts within the parameter callback that I cannot fully understand yet but I think might come in handy. Anyway it works as it is right now and I thank you for this.
1

If you want to keep your approach you could use the following. You would need to extend the if statement in case you have preferences which element should be kept in case there are two elements one with HDR set to true and one with 60fps set to true (with this version in this case the first resolution discovered would be selected):

const resolutions = [{
    "label": "4320p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "2160p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "2160p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "1440p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "1440p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "1080p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "720p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "480p",
    "features": {
      "HDR": true,
      "60fps": false
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "360p",
    "features": {
      "HDR": true,
      "60fps": false
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "240p",
    "features": {
      "HDR": true,
      "60fps": false
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": true,
      "60fps": true
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": false,
      "60fps": false
    }
  },
  {
    "label": "144p",
    "features": {
      "HDR": true,
      "60fps": false
    }
  }
]


const result = resolutions.reduce((unique, o) => {
  const uniqueResolution = unique.find((resolution) => resolution.label === o.label)

  if (!uniqueResolution) {
    return unique.concat(o)
  } else if (uniqueResolution.features.HDR && uniqueResolution.features['60fps']) {
    return unique
  } else if (o.features.HDR && o.features['60fps']) {
    // swap element since there is a better one
    return unique.map((resolution) => {
      if (resolution.label === o.label) return o
      return resolution
    })
  }

  return unique
}, [])

console.log(result)

Comments

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.