I need help for merging values in object array - Javascript - javascript

I am working with one project , I have the data comes to me as Object Array and I need to combine the same keys in one key and make the value as an array of strings.
here is the data I have :
inputArray = [
{
colors: 'Red',
size: 'Small'
},
{
colors: 'Blue',
size: 'Large'
},
{
colors: 'Red',
size: 'Large'
},
{
colors: 'Pink',
size: 'X-Large'
}
]
and here is the required output :
outputArray = {
colors: ['Red','Blue','Pink'],
size: ['Large','X-large','Small']
}

You could use a simple dictionary structure to do this. And verify if every element already exists before adding it to array.
const outputArray = {
colors: [],
size: [],
};
for (elem of inputArray) {
if (!outputArray['colors'].includes(elem.colors)) {
outputArray['colors'].push(elem.colors);
}
if (!outputArray['size'].includes(elem.size)) {
outputArray['size'].push(elem.size);
}
}
which will give
{
colors: [ 'Red', 'Blue', 'Pink' ],
size: [ 'Small', 'Large', 'X-Large' ]
}

it's a basic one...
const inputArray =
[ { colors: 'Red', size: 'Small' }
, { colors: 'Blue', size: 'Large' }
, { colors: 'Red', size: 'Large' }
, { colors: 'Pink', size: 'X-Large'}
];
outputArray = inputArray.reduce((a,c)=>
{
if (!a.colors.includes(c.colors) ) a.colors.push( c.colors);
if (!a.size.includes(c.size) ) a.size.push( c.size);
return a
}
,{ colors:[], size:[]})
;
console.log (outputArray )
[edit] if you do not know the variety of entry keys, you can use:
inputArray =
[ { colors: 'Red', size: 'Small' }
, { colors: 'Blue', size: 'Large' }
, { colors: 'Red', size: 'Large' }
, { colors: 'Pink', size: 'X-Large', truc: 'bidule' }
];
outputArray = inputArray.reduce((a,c)=>
{
for (let key in c)
{
if (!a[key]) a[key] = []
if (!a[key].includes(c.colors) ) a[key].push( c[key])
}
return a
} ,{})
;
console.log (outputArray)

This seems to work...
let inputArray = [
{
colors: 'Red',
size: 'Small'
},
{
colors: 'Blue',
size: 'Large'
},
{
colors: 'Red',
size: 'Large'
},
{
colors: 'Pink',
size: 'X-Large'
}
]
let outputArray = [{colors: [], size: []}]
for (let i = 0; i<inputArray.length; i++){
outputArray[0].colors.push(inputArray[i].colors)
outputArray[0].size.push(inputArray[i].size)
}
console.log(outputArray)
Is this what you were after?

While this is not logically much different from the second part of the answer by Mister Jojo, it does the same thing without any mutations, in perhaps a more functional manner:
const gather = (xs) =>
xs .reduce (
(a, x) => Object .entries (x) .reduce ((a, [k, v]) => ({...a, [k]: (a[k] || []).concat(v)}), a),
{}
)
const inputArray = [{ colors: 'Red', size: 'Small'}, {colors: 'Blue', size: 'Large'}, {colors: 'Red', size: 'Large'}, {colors: 'Pink', size: 'X-Large'}]
console .log (gather (inputArray))
It is likely less performant than that version, for reasons described by Rich Snapp, but in practice I haven't seen this being a real issue.

Related

Plotting bars with react with different colors, but they're all in black why?

I want to plot my historgram bar with different colors for A, B, C but I have my 3 bars with the same color i.e black, I guess it's in the fill field issue. If I plot only one bar then it's printed wirth the right color, but when it's more than 1 then it's all black.
I'm using recharts
Any idea please ?
export default function Overview() {
const { ratingBar } = graphs ?? {}
const COLORS = {
'A': 'red',
'B': 'green',
'C': 'orange',
}
function ratingGraph() {
const data = ratingBar
return ({
graph: {
type: 'bar',
showTooltip: true,
showXAxis: true,
showYAxis: true,
showLegend: true,
container: {
data,
},
bars: [
{
dataKey: 'count',
fill: COLORS[data.map((m) => m.title)],
}
],
xaxis: {
dataKey: 'title',
tick: { style: { fontSize: '.9rem' } },
},
yaxis: {
dataKey: 'count',
domain: [0, 100],
},
},
}).graph
}
return (
<div >
{...ratingGraph()}
</div>
)
}
Here is my data from my api :
{
"ratingBar": [
{
"title": "A",
"count": 2
},
{
"title": "B",
"count": 48
},
{
"title": "C",
"count": 78
}
]
}
you have to use cells property :
cells: data.map(m => ({fill: COLORS[m.title]}))
fill: data.map(m => COLORS[m.title])
Yours map is wrong. I guess the fill expects an array of colors. But you are accessing COLORS key with a mapped array which will be always undefined.

How can I group an array of objects by key and create another object inside

Does anyone know how to group an array of objects by an object key and then create a new array of objects based on the grouping? For example, I have an array of Build objects as below and I want to group by products and create another object of colors and price based on that.
build = [
{
'Product': 'Cabinets',
'Color': 'Blue',
},
{
'Product': 'CounterTop',
'Color': 'White',
},
{
'Product': 'Cabinets',
'Color': 'Yellow',
},
{
'Product': 'Cabinets',
'Color': 'Yellow',
}
]
And I want it like this
[
{
'Product':'Cabinet',
'color' : { 'Blue','Yellow' }
},
{
'Product':'CounterTop',
'color' : { 'White' }
}
]
I wrote a code to archive it but I am not getting the result as expected.
build.forEach(pr => {
if (pr.Product in result) {
result[pr['Product']]['Color'] = pr['Color'];
}
else {
result[pr['Product']] = {
'Product': pr['Product'],
'Color': pr['Color']
}
}
});
Above code returns
[
{
'Product':'Cabinet',
'color' : 'Yellow'
},
{
'Product':'CounterTop',
'color' : 'White'
}
]
Expecting 'color' : { 'Blue','Yellow' } in your output is wrong. Objects are key-value pairs of data.
Instead, you want color to be an array. I adjusted your code:
build.forEach(pr => {
if (pr.Product in result) {
result[pr['Product']]['Color'].push(pr['Color']);
} else {
result[pr['Product']] = {
'Product': pr['Product'],
'Color': [pr['Color']]
}
}
});
Now think about how you can prevent duplicate values in the array. #Lissy93's answer helps with that by using findIndex.
Here's a working version. Hope it helps :)
const builds = [
{ 'Product': 'Cabinets', 'Color': 'Blue' },
{ 'Product': 'CounterTop', 'Color': 'White' },
{ 'Product': 'Cabinets', 'Color': 'Yellow' },
{ 'Product': 'Cabinets', 'Color': 'Yellow' }
];
const results = [];
builds.forEach((build) => {
const index = results.findIndex((b) => b.Product === build.Product);
if (index === -1) {
results.push({Product: build.Product, Color: [ build.Color ]});
} else {
results[index] = {Product: build.Product, Color: [ ...results[index].Color, build.Color ]}
}
});
console.log(results);
The main issue in your code, was that you're mixing up arrays and key-value-pairs for color. KVP's look like { color: 'red' }, whereas arrays would be: [ 'red', 'blue'].
Use this strategy:
Find unique products
Based on the unique products use Array#map and Array#filter to build desired data
See demo below:
const builds = [ { 'Product': 'Cabinets', 'Color': 'Blue' }, { 'Product': 'CounterTop', 'Color': 'White' }, { 'Product': 'Cabinets', 'Color': 'Yellow' }, { 'Product': 'Cabinets', 'Color': 'Yellow' } ],
output = [...new Set(builds.map(({Product}) => Product))]
.map(Product =>
({
Product,
Color:builds.filter(({Product:P}) => P === Product)
.map(({Color}) => Color)
})
);
console.log(output);

Using variable in object method results in "Unknown Format"

When I'm trying to use the provided example at this pageenter link description here, react simply returns with "Unknown Format" at the first line using const color = chroma(data.color);
import chroma from "chroma-js";
const runeColorStyles = {
control: styles => ({ ...styles, backgroundColor: 'white' }),
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
const color = chroma(data.color); // First error is here
return {
...styles,
backgroundColor: isDisabled
? null
: isSelected
? data.color
: isFocused
? color.alpha(0.1).css()
: null,
color: isDisabled
? '#ccc'
: isSelected
? chroma.contrast(color, 'white') > 2
? 'white'
: 'black'
: data.color,
cursor: isDisabled ? 'not-allowed' : 'default',
':active': {
...styles[':active'],
backgroundColor: !isDisabled && (isSelected ? data.color : color.alpha(0.3).css()),
},
};
},
multiValue: (styles, { data }) => {
const color = chroma(data.color);
return {
...styles,
backgroundColor: color.alpha(0.1).css(),
};
},
multiValueLabel: (styles, { data }) => ({
...styles,
color: data.color,
}),
multiValueRemove: (styles, { data }) => ({
...styles,
color: data.color,
':hover': {
backgroundColor: data.color,
color: 'white',
},
}),
};
I've got no clue why this is happening on my side. It's very weird because it works perfectly fine in their example page.
This one occurred to me when using CreatableSelect and was trying to type a new option. I used const color = chroma(data.color ?? 'black'); to fix it.
const color = chroma(data.color);
The first step is to get your color into chroma.js. That's what the generic constructor chroma() does. This function attempts to guess the format of the input color for you. so the data.color requires a valid color.
colourStyles = {
control: styles => ({ ...styles, backgroundColor: 'white' }),
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
const color = chroma(data.color);
return {
......
};
}
colourOption object which you pass to the options will be available as the data - option: (styles, { data, isDisabled, isFocused, isSelected }). if the color is not present in the colourOption object it will through an error “Unknown Format”.
<Select className="basic-single" classNamePrefix="select" defaultValue={colourOptions[0]}
isDisabled={isDisabled} isLoading={isLoading} isClearable={isClearable}
isRtl={isRtl} isSearchable={isSearchable} name="color"
options={colourOptions} <!-- colourOptions object -->
styles={colourStyles} />
colourOptions object :
colourOptions:[
{ value: 'ocean', label: 'Ocean', color: '#00B8D9', isFixed: true },
{ value: 'blue', label: 'Blue', color: '#0052CC', isDisabled: true },
{ value: 'purple', label: 'Purple', color: '#5243AA' },
{ value: 'red', label: 'Red', color: '#FF5630', isFixed: true },
{ value: 'orange', label: 'Orange', color: '#FF8B00' },
{ value: 'yellow', label: 'Yellow', color: '#FFC400' },
{ value: 'green', label: 'Green', color: '#36B37E' },
{ value: 'forest', label: 'Forest', color: '#00875A' },
{ value: 'slate', label: 'Slate', color: '#253858' },
{ value: 'silver', label: 'Silver', color: '#666666' },
]
Note: Make sure that the color property exists in colourOptions.
Working Demo: https://codesandbox.io/s/react-codesandboxer-example-zxqg0

JavaScript ES6: How to Iterate Over Object with [Symbol()] names

I'm trying to figure out how to iterate over an object that is using Symbol names to uniquely identify properties. For instance, if I have this object:
const bowl1 = {
'apple': { color: 'red', weight: 136.078 },
'banana': { color: 'yellow', weight: 183.151 },
'orange': { color: 'orange', weight: 170.097 },
'peach': { color: 'yellow', weight: 176.845 }
};
for (var fruit in bowl1) {
var item = bowl1[fruit];
console.log(`${fruit}: `, item);
}
OUTPUT:
apple: { color: 'red', weight: 136.078 }
banana: { color: 'yellow', weight: 183.151 }
orange: { color: 'orange', weight: 170.097 }
peach: { color: 'yellow', weight: 176.845 }
// can even write your own iterator to get the same results
function* iterate_object(o) {
var keys = Object.keys(o);
for (var i = 0; i < keys.length; i++) {
yield [keys[i], o[keys[i]]];
}
}
// output is the same as above
for (var [key, val] of iterate_object(bowl1)) {
console.log(key, val);
}
However, if I change this object to use Symbols as such:
const bowl = {
[Symbol('apple')]: { color: 'red', weight: 136.078 },
[Symbol('banana')]: { color: 'yellow', weight: 183.15 },
[Symbol('orange')]: { color: 'orange', weight: 170.097 },
[Symbol('banana')]: { color: 'yellow', weight: 176.845 }
};
Note that symbols are used to keep the second banana from overwriting the first.
Anyway, neither method used above will iterate properly over this object.
Is there a way to iterate over objects using Symbol names?
Does it need to be created as a class and have an iterator method?
Thanks in advance for the help.
You can't get symbol property names because they're not stored as typical character/string values, but you can iterate over the list returned by Object.getOwnPropertySymbols and use those to pull information out of an Object.
const bowl = {
[Symbol('apple')]: { color: 'red', weight: 136.078 },
[Symbol('banana')]: { color: 'yellow', weight: 183.15 },
[Symbol('orange')]: { color: 'orange', weight: 170.097 },
[Symbol('banana')]: { color: 'yellow', weight: 176.845 }
};
for(let sym of Object.getOwnPropertySymbols(bowl) ) {
console.log(bowl[sym]);
}
As stated above you cannot get the symbol property names because they are not stored as typical character strings. However, the MDN site does say “In addition, Object.getOwnPropertyNames() will not return symbol object properties, however, you can use Object.getOwnPropertySymbols() to get these.”
As such, on order to get information about the objects use Object.getOwnPropertySymbols:
for(let sym of Object.getOwnPropertySymbols(bowl) ) {
console.log(sym, bowl[sym]);
}
OUTPUT:
Symbol(apple) { color: 'red', weight: 136.18 }
Symbol(banana) { color: 'yellow', weight: 183.15 }
Symbol(orange) { color: 'orange', weight: 170.97 }
Symbol(banana) { color: 'yellow', weight: 176.84 }
As you can see you don’t get the symbol names as strings, but it will return the symbol and their referenced objects.
You have to do some extra work if you want to convert the symbol to a string and just see the property name.
for(let sym of Object.getOwnPropertySymbols(bowl) ) {
// remove the 'Symbol (' from string
let symStr = String(sym).substring(7);
// now lop off the last char ')'
symStr = symStr.slice(0, -1);
console.log(symStr, bowl[sym]);
}
OUTPUT:
apple { color: 'red', weight: 136.18 }
banana { color: 'yellow', weight: 183.15 }
orange { color: 'orange', weight: 170.97 }
banana { color: 'yellow', weight: 176.84 }
You can combine the string functions on one line if you want.
let symStr = String(sym).substring(7).slice(0, -1);
Maybe not the best use for Symbols and there are both global and local symbols. The example used above and this is from lesson material are local.
Acutally there are two methods, will share it for your reference,
const bowl = {
[Symbol('apple')]: { color: 'red', weight: 136.078 },
[Symbol('banana')]: { color: 'yellow', weight: 183.151 },
[Symbol('orange')]: { color: 'orange', weight: 170.097 },
[Symbol('banana')]: { color: 'yellow', weight: 176.845 }
};
const bowlReflect = Reflect.ownKeys(bowl);
const ownPropertySymbols = Object.getOwnPropertySymbols(bowl);
**Method:1**
for(const fruit of bowlReflect){
console.log(bowl[fruit])
}
Method:2
for(const fruit of ownPropertySymbols){
console.log(bowl[fruit])
}
const fantasma = Symbol('fantasma');
console.log(fantasma);
// Symbol(fantasma)
const objetoLiteral = {
// propiedad: valor,
[fantasma]: '👻', // Symbol()
uno: 1,
dos: 2,
tres: 3,
};
console.log(objetoLiteral);
/*
{
uno: 1,
dos: 2,
tres: 3,
[Symbol(fantasma)]: '👻'
}
*/
Object.getOwnPropertySymbols(objetoLiteral).map((propiedad) => {
const valor = objetoLiteral[propiedad];
console.log(propiedad, '➜', valor);
});
// Symbol(fantasma) ➜ 👻

Javascript array/object/hash table to track cart availability

I don't know if this is possible but it seems there must be a simpler way to do this. I currently have a shopping cart for a t-shirt store. Each t-shirt has 3 drop down boxes to select prior to purchase:
Style:
American Apparel
Gildan
Size:
S
M
L
XL
Colour:
Blue
Black
White
Grey
Not every style is available in every size and colour combination but apart from how the cart is laid out on the page, there is no set way for the user to select style first, then size without forcing it which will be a barrier to making a sale.
Right now, when the user selects anything from any of the drop down boxes, an ajax call is made to the server to calculate what the other drop down boxes should contain, for example if the user selects Size (L) first, the colour may change to just Blue and Black as White and Grey are not available in Large, but worse than that White may be available but only in Gildan style.
Anyway, the ajax call has latency and can be especially slow on mobile devices with a spotty data connection. Is there a way I can achieve this with Javascript instead. I know all the combinations prior to rendering the page, and I can set up an array but get lost due to having more than two drop down boxes and end up with this ugly mess, and even then I don't know how to do the actual function which changes the boxes because multiple boxes may be selected:
<html>
<head>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
var styles = { aa: 'American Apparel', gi: 'Gildan' };
var sizes = { s: 'Small', m: 'Medium', l: 'Large' };
var colours = { blue: 'Blue', black: 'Black', white: 'White', grey: 'Grey' };
var availability = {
aa: { size: ['s', 'm', 'l'], colour: ['blue', 'black', 'white', 'grey'] },
gi: { size: ['s', 'm'], colour: ['blue', 'black', 'white', 'grey'] },
s: { style: ['aa', 'gi'], colour: ['blue', 'black', 'white'] },
m: { style: ['aa', 'gi'], colour: ['black', 'white', 'grey'] },
l: { style: ['aa'], colour: ['blue', 'black', 'white', 'grey'] },
blue: { style: ['aa', 'gi'], size: ['s', 'l'] },
black: { style: ['aa', 'gi'], size: ['s', 'm', 'l'] },
white: { style: ['aa', 'gi'], size: ['s', 'm', 'l'] },
grey: { style: ['aa', 'gi'], size: ['m', 'l'] }
};
$(function()
{
addOptions('style', styles);
addOptions('size', sizes);
addOptions('colour', colours);
});
function addOptions(name, data)
{
$('select[name="' + name + '"]').empty();
$.each(data, function(value, description)
{
$('select[name="' + name + '"]').append('<option value="' + value + '">' + description + '</option>');
});
}
function updateOptions(select)
{
// Work out what has changed, and update select boxes?
}
</script>
</head>
<body>
<select name="style" onchange="updateOptions(this);"></select>
<select name="size" onchange="updateOptions(this);"></select>
<select name="colour" onchange="updateOptions(this);"></select>
</body>
</html>
Is there a more efficient way to do this with a smarter function and/or hash table? These may not be the only three options, for example the store has pillows as well which have style, material, thread count and colour. Each set of options is unique to the product, but I know what they are prior to the page being rendered.
Thanks heaps.
The natural way to structure your data is with a multi-dimensional array (one dimension for each property) in which the values are true or false. I modeled it with that idea in mind, but with associative arrays (aka objects in JavaScript).
Data:
var availability = {
'American Apparel' : {
'S' : {
'black' : true,
'green' : true
},
'M' : {
'black' : true,
'white' : true
}
},
'Gildan' : {
'M' : {
'black' : true
},
'XL' : {
'black' : true,
'white' : true,
'green' : true
}
}
};
Now all you need is a function to return the possible options when some are selected. The first draft is below, but i'm sure it can be improved heavily. If a property is set, pass the value to the function, otherwise pass undefined. The function returns an object with 3 arrays indicating the valid options for the user's selection. Usage example at the end..
function pushIfNotIn(arr, item) {
if (arr.indexOf(item) === -1) arr.push(item);
}
function getAvailability(styleValue, sizeValue, colorValue) {
var av = {
style : [],
size : [],
color : []
};
for (var style in availability) {
if (styleValue === undefined || styleValue === style) {
for (var size in availability[style]) {
if (sizeValue === undefined || sizeValue === size) {
for (var color in availability[style][size]) {
if (colorValue === undefined || colorValue === color) {
if (availability[style][size][color]) {
pushIfNotIn(av.style, style);
pushIfNotIn(av.size, size);
pushIfNotIn(av.color, color);
}
}
}
}
}
}
}
return av;
}
console.log(getAvailability(undefined, 'M', undefined));
console.log(getAvailability('American Apparel', 'S', undefined));
console.log(getAvailability(undefined, 'M', 'black'));
console.log(getAvailability(undefined, 'M', 'green'));
console.log(getAvailability(undefined, undefined, 'green'));
DEMO: http://jsbin.com/uHAyirOX/1/edit
Obviously a more generic solution can be extrapolated from this method, with variable number of arguments and more levels in the availability object. Still, you have something to work it.
UPDATE: Generic solution (called in the same way)
function pushIfNotIn(arr, item) {
if (!arr) arr = [];
if (arr.indexOf(item) === -1) arr.push(item);
return arr;
}
function getAvailability() {
var result = [];
~function getAvailabilityRecursive (level, availability, values) {
if (!values.length) return true;
var isAvailable = false;
var val = values[0];
values = values.slice(1);
for (var key in availability) {
if ((val === undefined || val === key) &&
(getAvailabilityRecursive(level+1, availability[key], values))){
result[level] = pushIfNotIn(result[level], key);
isAvailable = true;
}
}
return isAvailable;
}(0, availability, Array.prototype.slice.call(arguments));
return result;
}
DEMO: http://jsbin.com/uHAyirOX/3/edit
Low-tech approach with the benefit of being immediately obvious for anyone maintaining it.
This could be an Ajax response, plain and simple:
var products = [
{ id: 101, style: 'aa', size: 's', colour: 'grey' },
{ id: 102, style: 'aa', size: 'm', colour: 'grey' },
{ id: 103, style: 'aa', size: 'l', colour: 'black' },
/* ... 500 more ... */
{ id: 604, style: 'gi', size: 'l', colour: 'blue' }
];
Now just filter that array brute-force on the client side:
function Drilldown(items, properties) {
var self = this,
numItems = items.length,
numProps = properties.length;
self.setFilter = function (filterDef) {
var i, item, p, prop, pass, filter = filterDef || {};
self.items = [];
self.properties = {};
for (i = 0; i < numItems; i++) {
item = items[i];
pass = true;
for (p = 0; pass && p < numProps; p++) {
prop = properties[p];
pass = pass && (!filter[prop] || filter[prop] === item[prop]);
if (!self.properties.hasOwnProperty(prop)) {
self.properties[prop] = {};
}
if (!self.properties[prop].hasOwnProperty(item[prop])) {
self.properties[prop][item[prop]] = [];
}
}
if (pass) {
self.items.push(item);
for (p = 0; p < numProps; p++) {
prop = properties[p];
self.properties[prop][item[prop]].push(item);
}
}
}
};
self.setFilter();
}
Usage:
var dd = new Drilldown(products, ['style', 'size', 'colour']);
dd.setFilter({size: 'l'});
/*
dd.items => [ array of size L products ]
dd.properties => {
style: {
aa: [ array of size L products in style 'aa' (1) ],
gi: [ array of size L products in style 'gi' (1) ]
},
size: {
s: [ array of size L products in size S (0) ],
m: [ array of size L products in size M (0) ],
l: [ array of size L products in size L (2) ]
},
colour: {
grey: [ array of size L products in Grey (0) ],
black: [ array of size L products in Black (1) ],
blue: [ array of size L products in Blue (1) ]
}
*/
dd.properties contains the all property combinations. Naturally some of the entries will be empty (array length 0), but all of them will be there. This makes indexing into this object straightforward.
I think I would have done something as:
var items = {
'item1':{
'level1' : {
'level2' : {},
'level2' : {'level3' :{}}
},
'level1' : {
'level2' : {},
'level2' : {'level3' :{}}
}
},
'item2':{
'level1' : {
'level2' : {},
'level2' : {'level3' :{}}
},
'level1' : {
'level2' : {},
'level2' : {'level3' :{}}
}
}
}
That a first selector(Style, for example) can dictates availabilities for next(Colour) and so on.
In any level user can change only levels below.

Categories

Resources