Create grouped object out of array of Strings in JavaScript - javascript

I have an issue I've not been able to solve, basically I want to transform this:
{
"seamark:name": "Z-2",
"seamark:type": "buoy_lateral",
"seamark:light:range": "5",
"seamark:light:colour": "red",
"seamark:light:character": "Q",
"seamark:radar_reflector": "yes",
"seamark:buoy_lateral:shape": "can",
"seamark:buoy_lateral:colour": "red",
"seamark:buoy_lateral:system": "iala-a",
"seamark:buoy_lateral:category": "port"
}
into this:
{
seamark: {
name: "Z-2",
type: "buoy_lateral",
light: {
range: "5",
colour: "red",
reflector: "yes"
},
buoy_lateral: {
shape: "can",
colour: "red",
system: "iala-a",
category: "port
}
}
}
For now I've only achieved to get an array containing 10 objects with every time the path to the value (for example {seamark: {name: "Z-2"}}) using the code shown in the following link:
codepen
Would anyone have an idea on how to group the properties deeply once I have the result shown in the codepen? Or maybe even another idea? thanks in advance

You are trying to unflat an object.
You can use flat npm (https://www.npmjs.com/package/flat)
const { unflatten } = require('flat');
const unflat = unflatten({
"seamark:name": "Z-2",
"seamark:type": "buoy_lateral",
"seamark:light:range": "5",
"seamark:light:colour": "red",
"seamark:light:character": "Q",
"seamark:radar_reflector": "yes",
"seamark:buoy_lateral:shape": "can",
"seamark:buoy_lateral:colour": "red",
"seamark:buoy_lateral:system": "iala-a",
"seamark:buoy_lateral:category": "port"
}, { delimiter: ":" }); // notice delimiter : default is "."
console.log(unflat);
Output:
{
seamark: {
name: 'Z-2',
type: 'buoy_lateral',
light: { range: '5', colour: 'red', character: 'Q' },
radar_reflector: 'yes',
buoy_lateral:
{
shape: 'can',
colour: 'red',
system: 'iala-a',
category: 'port'
}
}
}

For setting a value, you could split the path and reduce the path by walking the given object. If no object exist, create a new property with the name. Later assign the value.
function setValue(object, path, value) {
var last = path.pop();
path.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
}
var data = { "seamark:name": "Z-2", "seamark:type": "buoy_lateral", "seamark:light:range": "5", "seamark:light:colour": "red", "seamark:light:character": "Q", "seamark:radar_reflector": "yes", "seamark:buoy_lateral:shape": "can", "seamark:buoy_lateral:colour": "red", "seamark:buoy_lateral:system": "iala-a", "seamark:buoy_lateral:category": "port" };
Object
.keys(data)
.forEach(k => {
var keys = k.split(':');
if (keys.length === 1) return;
setValue(data, k.split(':'), data[k]);
delete data[k];
});
console.log(data);

You can also use "for..of" and "Array.reduce" like below
var obj = {
"seamark:name": "Z-2",
"seamark:type": "buoy_lateral",
"seamark:light:range": "5",
"seamark:light:colour": "red",
"seamark:light:character": "Q",
"seamark:radar_reflector": "yes",
"seamark:buoy_lateral:shape": "can",
"seamark:buoy_lateral:colour": "red",
"seamark:buoy_lateral:system": "iala-a",
"seamark:buoy_lateral:category": "port"
}
let newObj = {}
for(let [key, val] of Object.entries(obj)) {
let keys = key.split(':')
keys.reduce((o, d, i) => (
i == keys.length - 1
? (o[d] = val)
: (o[d] = o[d] || {})
, o[d])
, newObj)
}
console.log(newObj)

Related

Create array of objects from sorting another array of objects

I want to create an array of objects, and the first elements from the each data object to be a separate object, the second element from the each data object to be another separate object and so on...
let array = [{
data: {
center1: "1",
storage1: "1",
system1: "1",
}
},
{
data: {
center2: "2",
storage2: "2",
system2: "2",
}
}
]
Expected result:
[
{ center1: "1", center2: "2"},
{ storage1: "1", storage2: "2"},
{ system1: "1", system2: "2"}
]
And this is what I tried do to but is not working really well:)
const rows = [];
array.forEach((item, index) => {
for (let key in item.data) {
rows.push({index : key + ': ' + item.data[key]});
}
});
The output is this:
[
{index : 'center1: 1'},
{index : 'storage1: 1'},
{index : 'system1: 1'},
{index : 'center2: 2'},
{index : 'storage2: 2'},
{index : 'system2: 2'}
]
Thank you for your help!
This'll be incredibly brittle, because key ordering is irrelevant to how JS objects work, and the assumption that "the first key in each object is the same kind of key" is really only that: an assumption. So, the first thing to fix would be to make all those objects use the same keys, not uniques, thus making "key ordering" irrelevant.
However, if that's not an option (and it almost certain is, but if it's not) then Object.entries will turn any object into a key/value array, which you can then use to restructure this data:
let array = [{
data: {
center1: "1",
storage1: "1",
system1: "1",
}
},
{
data: {
center2: "2",
storage2: "2",
system2: "2",
}
}
]
const restructured = array.reduce((result, e) => {
Object.entries(e.data).forEach(([key, val], pos) => {
if (!result[pos]) result[pos] = {};
result[pos][key] = val;
});
return result;
}, []);
console.log(restructured);
You can try something like this, which may probably optimized (of course it will work only if all objects strictly have the same structure) :
const array = [
{
data: {
center1 : "1",
storage1: "1",
system1 : "1",
}
},
{
data: {
center2 : "2",
storage2: "2",
system2 : "2",
}
}
];
const finalArray = [];
for (let i = 0; i < Object.keys(array[0].data).length; i++) {
finalArray.push({});
array.forEach(arrayItem => {
finalArray[i][Object.keys(arrayItem.data)[i]] = Object.values(arrayItem.data)[i]
});
}
console.log(finalArray)
let array = [{
data: {
center1: "1",
storage1: "1",
system1: "1",
}
},
{
data: {
center2: "2",
storage2: "2",
system2: "2",
}
}
]
const rows = [];
array.forEach(item => {
Object.keys(item.data).forEach( (dt, i) => {
if ( ! rows[i] ) {
rows.push({});
}
rows[i][dt] = item.data[dt];
});
});
console.log(rows)

Split object keys with same prefix to new objects

For example, given an object with keys and values
{
prefix_1_a: 1a,
prefix_1_b: 1b,
prefix_2_a: 2a,
prefix_2_b: 2b,
}
I want convert into two objects:
prefix_1 with keys and values {a: 1a, b: 1b}
prefix_2 with keys and values {a: 2a, b: 2b}
another example ,given a formData object:
["item_0_orange":"10",
"item_0_apple:"20",
"item_0_grape":"30",
"item_1_orange":"40",
"item_1_apple":"50",
"item_1_grape":"60",
"item_2_orange":"40",
"item_2_apple":"50",
"item_2_grape":"60"]
and I want to convert to json object
fruitprice:
[
{key:0 ,orange:"10" , apple:"20" , grape:"30" },
{key:1 ,orange:"40" , apple:"50" , grape:"60" },
{key:2 ,orange:"40" , apple:"50" , grape:"60" }
]
how to search and add key and value under a position when match same prefix
here is my code:
var fruitObject ={};
for(i=0;i<3;i++)
{
var prefix = "item_" + i;
var res = key.split("_");
var newKey = res[2];
if(key.startsWith(prefix))
{
var newObject = {};
newObject[newKey] =value;
addObject(res[1],newObject, fruitObject); //by given key
return;
};
}
It can be a costly transformation, but not that complex:
Let's start with the input:
const data = {
"item_0_orange": "10",
"item_0_apple": "20",
"item_0_grape": "30",
"item_1_orange": "40",
"item_1_apple": "50",
"item_1_grape": "60",
"item_2_orange": "40",
"item_2_apple": "50",
"item_2_grape": "60",
}
Then have a look at the desired output:
const fruitprices = [
{
key: 0,
orange: "10",
apple: "20",
grape: "30"
},
{
key: 1,
orange: "40",
apple: "50",
grape: "60",
},
{
key: 2,
orange: "40",
apple: "50",
grape: "60",
}
]
And here's a transformation from data to fruitprices:
// this is an Object, not an Array!
const data = {
"item_0_orange": "10",
"item_0_apple": "20",
"item_0_grape": "30",
"item_1_orange": "40",
"item_1_apple": "50",
"item_1_grape": "60",
"item_2_orange": "40",
"item_2_apple": "50",
"item_2_grape": "60",
}
// transform function
const fruitprices = Object.entries(data).reduce((a, [key, val]) => {
// destructuring the key; "prefix" is not going to be used
const [prefix, outkey, fruit] = key.split('_')
// trying to find the item in the "a" Array
const item = a.find(({
key: itemkey
}) => itemkey === outkey)
if (item) { // if the key is already in the "a" Array
item[fruit] = val
} else { // if the key is not in the "a" Array yet
a.push({
key: outkey,
[fruit]: val
})
}
return a
}, [])
console.log(fruitprices)

JavaScript: Reduce an array to nested objects

So suppose my array looks like this:
let langArr = [
["python", "blue"]
,["python", "blue"]
,["c++", "red"]
,["java", "yellow"]
,["javascript", "lime"]
,["shell", "green"]
,["c++", "red"]
];
what I want is something like this:
{
python: {
count: 2
color: "blue"
}
c++: {
count: 2
color: "red"
}
java: {
count: 1
color: "yellow"
}
and so on...
}
I tried reduce method like this:
let langCount = langArr.reduce((lang, [name, color]) => {
lang[name] = (lang[name] || 0) + 1;
lang[color] = 'color';
return lang;
}, {});
console.log(langCount);
but I get this output:
{
python: 2
blue: "color"
c++: 2
red: "color"
java: 1
yellow: "color"
and so on...
}
You need an object for each language.
This approach takes an object as default value if lang[name] is falsy, like undefined.
The pattern
variable = variable || value;
works with a logical OR ||:
if variable has a truthy value, take this value,
if variable has a falsy value, take value instead.
let langArr = [["python", "blue"], ["python", "blue"], ["c++", "red"], ["java", "yellow"], ["javascript", "lime"], ["shell", "green"], ["c++", "red"]],
langCount = langArr.reduce((lang, [name, color]) => {
lang[name] = lang[name] || { count: 0, color };
lang[name].count++;
return lang;
}, {});
console.log(langCount);
You can use this:
array.reduce((acc, current) => {
if(!acc.hasOwnProperty(current[0])){
acc[current[0]] = {count: 0, color: current[1]};
}
acc[current[0]].count += 1;
return acc;
}, {});

filter array of object based on multiple properties

tl;dr
Im trying to deduplicate an array of objects that are not necessarily identical.
desc.
i have downloaded a set of records from a site that manages them poorly and there are many duplicates of records, though some with different names or different weights. What id like to do is compare each record to each other record and remove all entries but the one with the greatest weight, or all but one entry in the case that there is an exact duplicate (including name).
Arr obj
{
"entry" : "Single Lift",
"name" : "Jane Doe",
"sex" : "female",
"division": "40-44",
"equipped": "raw",
"wtclass" : 66,
"lift" : "bench press",
"weight" : 151
}
Pseudo
function dedupe(lift=records){
console.log(lift.length)
lift.forEach((record,index)=>{
for(i=0;i<lift.length;i++){
if(record.sex===lift[i].sex){
if(record.division===lift[i].division){
if(record.equipped===lift[i].equipped){
if(record.wtclass===lift[i].wtclass){
if(record.entry===lift[i].entry){
if(record.name===lift[i].name&&record.weight===lift[i].weight) lift.splice(i,1)
else if(record.weight>lift[i].weight) lift.splice(i,1)
else if(record.weight<lift[i].weight) lift.splice(index,1)
}
}
}
}
}
}
})
console.log(lift.length)
return lift
}
This code obviously doesn't operate the way i want it to, but it illustrates the intent. I would like to remove all but the greatest weight (or all but one where all key:values are identical) by comparing the weight on any entry where entry sex division equipped wtclass and lift are a match.
There are other questions around about deduplicating arrays of objects, but i could not find anything that matched my scenario or that i could adapt to work for me. If anyone has a solution (or a link to a solution) i would greatly appreciate it!
With those values you can concatenate them and check for the duplicates.
You can use the function reduce.
This example has two objects with the same values (but the weights) and one of them with different values.
var array = [
{
'entry': 'Single Lift',
'name': 'Jane Doe',
'g': 'f',
'division': '40-44',
'equipped': 'raw',
'wtclass': 66,
'lift':
'bench press', 'weight': 151
},
{
'entry': 'Single Lift',
'name': 'Jane Doe',
'g': 'f',
'division': '40-44',
'equipped': 'raw',
'wtclass': 66,
'lift': 'bench press',
'weight': 160
},
{
'entry': 'Single Lift',
'name': 'Ele',
'g': 'm',
'division': '40-44',
'equipped': 'raw',
'wtclass': 66,
'lift': 'bench press',
'weight': 151
}
]
var result = Object.values(array.reduce((a, o) => {
var {
entry,
name,
g,
division,
equipped,
wtclass, lift
} = o
var key = [
entry,
name,
g,
division,
equipped,
wtclass,
lift
].join('||')
if (!a[key] || o.weight > a[key].weight) {
a[key] = o
}
return a
}, {}))
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
const array = [
{ "entry" : "Single Lift", "name" : "Jane Doe", "sex" : "female", "division": "40-44", "equipped": "raw", "wtclass" : 66, "lift" : "bench press", "weight" : 151 }, { "entry" : "Single Lift", "name" : "Jane Doe", "sex" : "female", "division": "40-44", "equipped": "raw", "wtclass" : 66, "lift" : "bench press", "weight" : 152 }, { "entry" : "Single Lift", "name" : "Jane Doe", "sex" : "female", "division": "40-44", "equipped": "raw", "wtclass" : 66, "lift" : "bench press", "weight" : 151 }, { "entry" : "Double Lift", "name" : "Jane Doe", "sex" : "female", "division": "40-44", "equipped": "raw", "wtclass" : 66, "lift" : "bench press", "weight" : 151 }
]
const groupBy = (xs, exceptions) =>
xs.reduce((groups, element) => {
var key = Object.assign({}, element);
for (let i = 0; i < exceptions.length; i++) {
delete key[exceptions[i]];
}
key = Object.values(key).join(',');
if (!groups[key]) {
groups[key] = [element];
} else {
groups[key].push(element);
}
return groups;
}, {})
let grouppedRecords = groupBy(array, ['weight']);
let biggestWeightRecords = Object.values(grouppedRecords).map(records => {
let biggestWeightRecord = records[0];
for (let i = 0; i < records.length; i++) {
if (records[i].weight > biggestWeightRecord.weight) {
biggestWeightRecord = records[i];
}
}
return biggestWeightRecord;
});
console.log(biggestWeightRecords);
Solution with 2 iterations, admit any number of properties to filter by, two rules (bigger and smaller), just works if the values of filter properties are primitive values.
const elements = [
{
foo: 1,
bar: 2,
fooBard: 3,
},
{
foo: 2,
bar: 2,
fooBard: 6,
},
{
foo: 1,
bar: 2,
fooBard: 5,
},
{
foo: 2,
bar: 2,
fooBard: 3,
}
];
const RULE_BIGGER = 'RULE_BIGGER';
const RULE_SMALLER = 'RULE_SMALLER';
const elementsFiltered = new Map();
const createKey = (element, ...matchingProperties) =>
matchingProperties
.map(matchProperty => element[matchProperty] || '')
.join('#');
const hasToBeUpdated = (rule) => (oldValue, newValue) => {
switch (rule) {
case RULE_BIGGER:
return newValue > oldValue;
case RULE_SMALLER:
return newValue < oldValue;
default:
return false;
}
}
const filterByProperty = (elements, property, rule, ...matchingProperties) => {
const hasToBeUpdatedWithRule = hasToBeUpdated(rule);
elements.forEach((element) => {
const key = createKey(element, ...matchingProperties);
const storedElement = elementsFiltered.get(key)
if (!storedElement ||
hasToBeUpdatedWithRule(storedElement[property], element[property])) {
elementsFiltered.set(key, element);
}
});
const result = [];
elementsFiltered.forEach(elementFiltered => {
result.push(elementFiltered);
});
return result;
};
const result = filterByProperty(elements, 'fooBard', RULE_BIGGER, 'foo', 'bar');
console.log(result);

Single variable to stand in for multiple key names - javascript

I have an array, something like this:
array =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
I have a function that searches for values in the array.
function findValue (index, key) {
return array[index][key];
}
var value = findValue(0, "id");
// returns redApple
Is there a way I could pass a single argument to the function if I wanted to find something deeper in the array? For example, if I wanted to find "bunchName" could I pass it something like 1, "options[0].bunchName" and get back "bunch1"?
I want a function that can handle multiple keys. In my real project sometimes I'm looking for something on the first level, sometimes I'm looking on the second level, sometimes the third level, etc.
jQuery can be used if for some reason that would help.
You could take the string, replace the brackets, split the string and reduce the path for the result. The function uses a default object for missing or not given properties.
function getValue(object, path) {
return path
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.')
.reduce(function (o, k) { return (o || {})[k]; }, object);
}
function findValue(index, path) {
return getValue(array[index], path);
}
var array = [{ type: "apple", color: "red", id: "redApple" }, { type: "grape", color: "green", id: "greenGrape", options: [{ bunchName: "bunch1", size: "8" }, { bunchName: "bunch2", size: "10" }, { bunchName: "bunch3", size: "5" }] }];
console.log(findValue(1, "options[0].bunchName"));
From what I understand, output of findValue(object, "bunchName"); should be "bunch3", where object is array in OP's example.
var object =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
var findValue = (object, key) => {
var resultValue;
var rec = (currentObj) => {
if(currentObj && typeof currentObj === "object"){
for(let curKey in currentObj){
if (curKey === key){
resultValue = currentObj[curKey];
}else{
rec(currentObj[curKey]);
}
}
}
}
rec(object);
return resultValue;
}
console.log(findValue(object, "bunchName"));
You could add a function that takes an object and a key and returns object[key] and then split your key string into a list of individual keys by the dot. Then you could traverse the list of keys and use the function to get the value for each level in your object:
Totally untested code I just whipped up:
function valueByKey(obj, key) {
if (obj) {
return obj[key];
}
}
function findValue(index, key) {
const keys = key.split('.');
let value = array[index];
for (let i = 0; i < keys.length; i++) {
value = valueByKey(value, keys[i]);
}
return value;
}
Non-recurrent solution:
var array = [
{
'a': {
'b': 1
}
}
];
function findValue(index, key) {
var keys = key.split('.');
var tmp = array[index];
for (var i = 0; i < keys.length; i++) {
if (!tmp.hasOwnProperty(keys[i]) || typeof tmp !== 'object') {
// throw an exception, or return default value – property not found.
}
tmp = tmp[keys[i]];
}
return tmp;
}
findValue(0, 'a.b');

Categories