Group list of objects by JSON key - javascript

I am trying to reformat my list of objects by grouping a certain key pair in javascript.
Data Format
[{
"sccode": "sccode1",
"val": "1ADA"
}, {
"sccode": "sccode2",
"val": "1ADB"
}, {
"sccode": "sccode1",
"val": "1ADC"
}]
Expected Result
[{
"scCode": "sccode1",
"valCodes": ["1ADA", "1ADC"]
},
{
"scCode": "sccode2",
"valCodes": ["1ADB"]
}
]
I believe I could loop through the array and match my keys, but is there a quick way to reformat this without having to explicitly loop through? I've tried using a reduce function below, but it gives undefined errors with find, which i think has something to do with my formatting.
Tried (?) Code
const resp = data.reduce((acc, ele) => {
const ant = acc.find(x => x.sccode === ele.sccode);
}, []);

Would this do?
const src = [{"sccode":"sccode1","val":"1ADA"},{"sccode":"sccode2","val":"1ADB"},{"sccode":"sccode1","val":"1ADC"}],
result = src.reduce((r,{sccode,val}) => {
const match = r.find(({scCode}) => scCode == sccode)
match ?
match.valCodes.push(val) :
r.push({scCode:sccode, valCodes: [val]})
return r
}, [])
console.log(result)
.as-console-wrapper{min-height:100%;}

Try the following, I use a map to store a partial state to improve performances preventing to search sccode in an array for every initial object.
let partial = [{
"sccode": "sccode1",
"val": "1ADA"
}, {
"sccode": "sccode2",
"val": "1ADB"
}, {
"sccode": "sccode1",
"val": "1ADC"
}].reduce((map, obj) => {
if (!map[obj.sccode]) {
map[obj.sccode] = [obj.val];
} else {
map[obj.sccode].push(obj.val);
}
return map;
}, {})
Object.keys(partial).map(sccode => ({
sccode, valCodes: partial[sccode]
}));

try loaddash/groupby
let groupByResult = groupBy(data, function (n) {
return n.sccode
});

Check this code:
array.reduce(function(res, value){
if(!res[value.sccode]) {
res[value.sccode] = value;
res[value.sccode]['valCodes'] = []
result.push(res[value.sccode]);
}
res[value.sccode]['valCodes'].push(value.val);
return res;
} ,{});
I tested here and it works fine!

Related

Find string in array of objects

I have two different arrays and I am trying to filter the one while looping the other.
const serviceCodes =
[
{
...otherStuff...
"codes": [
"786410010",
"787885010"
]
}
]
and
const laborCost =
[
{
"laborCode": "786410010",
"estimatedCost": {
"value": -1,
"currency": "USD"
}
},
{
"laborCode": "787885010",
"estimatedCost": {
"value": -1,
"currency": "USD"
}
}
]
I am looping through serviceCodes and trying to return only the object that matches the laborCode
const serviceMatchesRow = serviceCodes[0].codes.forEach((code) => {
return laborCost?.find(
(service) => service.laborCode === code,
)
})
This is returning undefined as forEach only runs through it, but if I use .map instead, then it return the 2 objects within laborCost. If I change for .find instead, than it returns an array with the laborCode itself ["786410010", 787885010].
So how can I get around this?
The desired output would be something like:
[{
"laborCode": "786410010",
"estimatedCost": {
"value": -1,
"currency": "USD"
}
}]
forEach method doesn’t return anything
so you should insert the matches objects in new array.
or use .map method
const serviceMatchesRow = [];
serviceCodes[0].codes.map((code) => {
serviceMatchesRow.push(laborCost.find((service) => {
return service.laborCode === code
}
));
});
Problem: You're trying to return from within the foreach block; that will not return anything thats why you see undefined.
Solution: Use map to return while iterating on an array.
const serviceMatchesRow = serviceCodes[0].codes.map((code) => {
return laborCost.find(
(service) => service.laborCode === code,
)
})
Working fiddle: https://jsfiddle.net/240z5u3f/3/

multiselect filter array with nested object

I use vue-multiselect to let user select items with filtering options:
query = "what user type in input field"
allLinks.filter(l=>l.labelName.includes(query))
and it works, but now I would like to extend the filtering to all properties of my object with this structure :
{
"labelName":"LK000056",
"extPort":{
"aPort":"EXTA-EQ001/board10port02",
"zPort":"EXTZ-EQ012/board09port02"
}
}
I would like with one query to get parent object if query match on labelName, aPort or zPort.
it is possible ? or maybe with a conditional way like :
allLinks.filter(l=>if(!l.labelName.includes(query)){l.extport.aPort.includes(query)}else{l.extport.zPort.includes(query)})
thank for helping me
forgive my approximate English I am French
You could recursively flatten the objects to array of strings, then search in them via Array.filter, Array.some & Array.includes:
const data = [{ "labelName":"LK000056", "extPort":{ "aPort":"EXTA-EQ001/board10port02", "zPort":"EXTZ-EQ012/board09port02" } }, { "labelName":"LK000057", "extPort":{ "aPort":"EXTA-EQ001/board123", "zPort":"EXTZ-EQ012/board333" } }]
const flatten = (obj, a=[]) => Object.values(obj)
.reduce((r,c) => (typeof c == 'object' ? flatten(c,a) : r.push(c), r), a)
const search = (d, t) =>
d.filter(x => flatten(x).some(x => x.toLowerCase().includes(t.toLowerCase())))
console.log(search(data, 'board123'))
console.log(search(data, 'LK000056'))
console.log(search(data, 'EXTZ-EQ012'))
Note that this is a generic approach and will work regardless of the nested levels of data, for example:
const data = [{
"A": {
"B": {
"C": {
"data": '11'
},
}
}
}, {
"D": {
"E": {
"data": '22'
}
}
}, {
"F": "33"
}]
const flatten = (obj, a = []) => Object.values(obj)
.reduce((r, c) => (typeof c == 'object' ? flatten(c, a) : r.push(c), r), a)
const search = (d, t) => d.filter(x =>
flatten(x).some(x => x.toLowerCase().includes(t.toLowerCase())))
console.log(search(data, '22'))
console.log(search(data, '11'))
console.log(search(data, '33'))
You can use Object.values to get the values of the object as an array, then use some to test if one or more items match.
const items = [{
"labelName": "LK000056",
"extPort": {
"aPort": "EXTA-EQ001/board10port02",
"zPort": "EXTZ-EQ012/board09port02"
}
}, {
"labelName": "234234",
"extPort": {
"aPort": "abc123",
"zPort": "1234567890"
}
}]
function search(query, data) {
return data.filter(i => {
if (i.labelName.includes(query)) return true
if (Object.values(i.extPort).some(v => v.includes(query))) return true
return false
})
}
console.log(search("EQ001", items))
console.log(search("1234567890", items))
console.log(search("lalalala", items))
if it can help I think found a functional solution let me know if something better can be done ...
allLinks.filter(l=>{
if (l.sdhPort.zSDHPort!=undefined && l.extPort.aPort.toUpperCase().includes(query.toUpperCase())){return true}
if (l.extport.zPort!=undefined && l.extPort.zPort.toUpperCase().includes(query.toUpperCase())){return true}
if (l.labelName!=undefined && l.labelName.toUpperCase().includes(query.toUpperCase())){return true}
})
wow thanks both of you !
it seems really interesting this technic ... but let me some time to understand it
what do think about what I found ?
best regards

Grouping a data set by the key of an object and producing an array

I have a set of data which I have grouped by the key of the object - For each key of the object I need to produce an array of the id's . So instead of an array of object it is just a flat array of the id's.
I have managed to get the data back but can't seem to get just the id's out.
Here is the data set
let result = {
"age_group_ids": [
{
"id": 5
},
{
"id": 4
},
{
"id": 3
},
{
"id": 9
}
],
"family_group_ids": [
{
"id": 2
},
{
"id": 1
}
],
"earnings_group_ids": [
{
"id": 7
},
{
"id": 6
}
]
}
This is the function I have written but it does not seem to work.
Object.keys(result).forEach(key => {
return result[key].map(item => {
item.id
})
})
The end result I want an object like this
{"age_group_ids": [5,4, 3, 9],
"family_group_ids": [2, 1],
"earnings_group_ids": [7,6]
Instead of returning from the forEach, assign the results back the result[key]. Removed the curly brackets arround the item.id of the map, so value would be added to the array.
const result = {"age_group_ids":[{"id":5},{"id":4},{"id":3},{"id":9}],"family_group_ids":[{"id":2},{"id":1}],"earnings_group_ids":[{"id":7},{"id":6}]};
Object.keys(result).forEach(key => {
result[key] = result[key].map(item => item.id)
})
console.log(result);
let result = {"age_group_ids":[{"id":5},{"id":4},{"id":3},{"id":9}],"family_group_ids":[{"id":2},{"id":1}],"earnings_group_ids":[{"id":7},{"id":6}]};
function groupById(groups) {
return Object.keys(groups).reduce((result, key) => {
result[key] = result[key] || [];
result[key] = groups[key].map(o => o.id);
return result;
}, {});
}
console.log(groupById(result))
Use Array.prototype.reduce() instead of Array.prototype.forEach(), and Object.entries() for convenience so you don't have to use a scoped reference to result[key]. You can also use object destructuring to reference key, values, and id in a more concise way. Don't forget that you need to initialize acc to an empty object for the reduce() callback:
const result = {age_group_ids:[{id:5},{id:4},{id:3},{id:9}],family_group_ids:[{id:2},{id:1}],earnings_group_ids:[{id:7},{id:6}]}
const output = Object.entries(result).reduce((acc, [key, values]) => Object.assign(acc, {
[key]: values.map(({ id }) => id)
}), {})
console.log(output)

Parsing a value from JSON response that keeps changing order

Im trying to parse JSON response from my home automation system in javscript.
The response is
available here
That is only a small part of the response, also, the order of keys changes on every reboot for reasons i do not know, su using the the number index wont really work
i need to be able to store the state value of sensor.out, sensor.in, sensor.door into variables for tasker on andorid
i tried to select using the entity.id, but for some reason the code never finished ( i believe i just didnt know what i was doing)
With ES6 you can use the Array#find method:
response.find(o => o.entity_id == 'sensor.out').state
See snippet:
var response = [ { "attributes":{ "friendly_name":"door" }, "entity_id":"sensor.door", "last_changed":"2016-12-31T11:15:59.395808+00:00", "last_updated":"2016-12-31T11:15:59.395808+00:00", "state":"closed" }, { "attributes":{ "friendly_name":"In", "unit_of_measurement":"\u00b0C" }, "entity_id":"sensor.in", "last_changed":"2016-12-31T11:20:02.179821+00:00", "last_updated":"2016-12-31T11:20:02.179821+00:00", "state":"20.7" }, { "attributes":{ "changed_by":null, "code_format":".+", "friendly_name":"panel" }, "entity_id":"alarm_control_panel.panel", "last_changed":"2016-12-31T11:14:56.471966+00:00", "last_updated":"2016-12-31T11:14:56.471966+00:00", "state":"disarmed" }, { "attributes":{ "friendly_name":"Out", "unit_of_measurement":"\u00b0C" }, "entity_id":"sensor.out", "last_changed":"2016-12-31T11:14:58.452345+00:00", "last_updated":"2016-12-31T11:14:58.452345+00:00", "state":"7.1" }];
var state = response.find(o => o.entity_id == 'sensor.out').state;
console.log('sensor.out state is', state);
Alternatively, you could convert the response to an object with the entity id values as keys, so you can access it like response['session.out'].state:
response = Object.assign({}, ...response.map( o => ({[o.entity_id]: o}) ));
See snippet:
var response = [ { "attributes":{ "friendly_name":"door" }, "entity_id":"sensor.door", "last_changed":"2016-12-31T11:15:59.395808+00:00", "last_updated":"2016-12-31T11:15:59.395808+00:00", "state":"closed" }, { "attributes":{ "friendly_name":"In", "unit_of_measurement":"\u00b0C" }, "entity_id":"sensor.in", "last_changed":"2016-12-31T11:20:02.179821+00:00", "last_updated":"2016-12-31T11:20:02.179821+00:00", "state":"20.7" }, { "attributes":{ "changed_by":null, "code_format":".+", "friendly_name":"panel" }, "entity_id":"alarm_control_panel.panel", "last_changed":"2016-12-31T11:14:56.471966+00:00", "last_updated":"2016-12-31T11:14:56.471966+00:00", "state":"disarmed" }, { "attributes":{ "friendly_name":"Out", "unit_of_measurement":"\u00b0C" }, "entity_id":"sensor.out", "last_changed":"2016-12-31T11:14:58.452345+00:00", "last_updated":"2016-12-31T11:14:58.452345+00:00", "state":"7.1" }];
response = Object.assign({}, ...response.map( o => ({[o.entity_id]: o}) ));
console.log('sensor.out state is', response['sensor.out'].state);
If you're trying to uses indexes to select properties from objects, you shouldn't be, unless there is a very specific reason to do so.
Fortunately that's fine, you don't need to know the order. I took two of the objects from your JSON array, scrambled up the properties, and wrote a function that returns any object that contains the key/val you specify.
Your question is a little hard to follow, but I think this will give you the idea.
<script type="text/javascript">
let arr = [
{
"attributes":{
"friendly_name":"door"
},
"entity_id":"sensor.frontdoor",
"last_changed":"2016-12-31T11:15:59.395808+00:00",
"last_updated":"2016-12-31T11:15:59.395808+00:00",
"state":"closed"
},
{
"last_changed":"2016-12-31T11:15:59.395808+00:00",
"state":"closed",
"attributes":{
"friendly_name":"door"
},
"entity_id":"sensor.backdoor",
"last_updated":"2016-12-31T11:15:59.395808+00:00"
}
];
function findKey ( theKey, theVal ) {
let reduced = arr.filter ( d => {
return d [ theKey ] === theVal;
});
return reduced;
}
let targets = findKey ( 'entity_id', 'sensor.backdoor' );
targets.forEach ( d => {
// This check is a little naive, but should give you the idea
if ( 'state' in d ) {
console.log ( d.state );
}
} );
</script>
What everyone says is correct: The order of keys in the response doesn't matter. Use the (string) key, not a numerical index.
var arr = [
{
"entity_id":"sensor.door",
"state":"closed"
}]; // other attributes chopped out for brevity
var entity_id_interested_in = 'sensor.door';
var state = '[entity_id not found in response]';
for (var i = 0; i < arr.length; i++) {
console.log(arr[i].entity_id + ' state:' + arr[i].state);
if (arr[i].entity_id == entity_id_interested_in)
{
state = arr[i].state;
break;
}
}
console.log (state);

Advanced Array.prototype.filter with Javascript

I have an Javascript object like so...
var strategies = [{
"strategy": {
"category": "war"
}
}, {
"strategy": {
"category": "farming"
}
}]
I then have an array that indicates which results I'd like back. It can be any of the following: [] OR ["war"] ["farming"] OR ["war", "farming"].
If we have the [], I want to return no results. But if ["war", "farming"] I want to return both of the results above.
How do I accomplish this with Array.prototype.filter? I saw this post, but couldn't reason through it.
strategies.filter((strategy) =>
????
)
Thanks for your help.
You can just check the value with indexOf:
var categories = ['war', 'farming'];
var filtered = strategies.filter((obj) => {
return categories.indexOf(obj.strategy.category) > -1;
});
Your object, strategy was a wrapped object, so my first line was setting it to its inner strategy and then filter as needed.
var strategies = [{
"strategy": {
"category": "war"
}
}, {
"strategy": {
"category": "farming"
}
}]
var b = ["war", "farming"];
strategies.filter(function(strategy){
strategy = strategy.strategy;
for(var i in b){
if (b[i] == strategy["category"]) {
return true;
}
}
return false;
});
Tests the input array to see if it's empty as per your requirements:
function filterObj(arr) {
return !arr.length ? arr :
strategies.filter((el) => arr.indexOf(el.strategy.category) > -1);
}
filterObj(['war', 'farming'])
DEMO

Categories

Resources