Parsing a value from JSON response that keeps changing order - javascript

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);

Related

Issues with geting results from filter inside filters

Guys i'm trying to filter some info from this array, a piece of it:
{
"entry": [
{
"ent_seq": 1000090,
"k_ele": [
{
"keb": "○"
},
{
"keb": "〇"
}
],
"r_ele": {
"reb": "まる"
},
"sense": [
{
"pos": "&n;",
"xref": "〇〇・まるまる・1",
"gloss": "symbol used as a placeholder (either because a number of other words could be used in that position, or because of censorship)"
},
{
"pos": "&n;",
"xref": "句点",
"gloss": [
"period",
"full stop"
]
},
{
"pos": "&n;",
"xref": "半濁点",
"gloss": [
"maru mark",
"semivoiced sound",
"p-sound"
]
}
]
},
Here we have the 'sense' item, where he can be an array or not, and inside of it the 'gloss' item, an array or not as well.
To do the main search, im doing this:
export const mainSearch = async (req, res) => {
var filterData2 = teste.entry.filter(x => {
if ('sense' in x && Array.isArray(x['sense'])) {
let result = x.sense.filter(sense_item => {
if (Array.isArray(x.sense['gloss'])) {
let result2 = sense_item.gloss.includes(req.params.id)
} else if (x.sense.gloss === req.params.id) return x
})
}
if(result) return x
}
)
if (filterData2) {
console.log(filterData2)
// res.json(filterData2)
}
Where i receive the search item from req.params.id, and i already tried dozens of things, im really stucked here, for the result right now i'm getting an empty array
The aim is if i get a true response for the search, to let the 'first filter' perceive it and go checking the rest.
For the 'k_ele' and 'r_ele' my code works fine too, a piece of it:
if ('k_ele' in x && Array.isArray(x['k_ele'])) {
let result = x.k_ele.some(el =>
el.keb.includes(req.params.id)
)
if (result) return x
} else
if ('k_ele' in x && x.k_ele.keb === req.params.id) return x
I'd suggest that you change your strategy. If the data is hard to filter and loop through, then it might be a good idea to change the data to something that is easier to work with.
In the end you'd want to check if the req.params.id is found in any of the gloss arrays or strings. Therefor it might be a good idea to collect all items in the gloss items into a single array and check if the queried value is found in any of the strings.
const data = [
"symbol used as a placeholder (either because a number of other words could be used in that position, or because of censorship)",
"period",
"full stop",
"maru mark",
"semivoiced sound",
"p-sound"
]
With the data like the example above, you'd only have to evaluate if the string you're looking for is present in the array.
const isFound = data.includes(value)
const teste={"entry":[{"ent_seq":1000090,"k_ele":[{"keb":"○"},{"keb":"〇"}],"r_ele":{"reb":"まる"},"sense":[{"pos":"&n;","xref":"〇〇・まるまる・1","gloss":"symbol used as a placeholder (either because a number of other words could be used in that position, or because of censorship)"},{"pos":"&n;","xref":"句点","gloss":["period","full stop"]},{"pos":"&n;","xref":"半濁点","gloss":["maru mark","semivoiced sound","p-sound"]}]}]};
// Example search request.
var req = {
params: {
id: 'full stop'
}
}
/**
* Check if the sense property is present and is an array.
* Then return an array of all sense objects.
*/
const senseEntries = teste.entry
.filter(entry => 'sense' in entry && Array.isArray(entry.sense))
.flatMap(({
sense
}) => sense)
/**
* Loop over the filtered sense objects and return
* either the gloss array or the gloss string inside of an array.
*/
const glossEntries = senseEntries
.flatMap(({
gloss
}) => Array.isArray(gloss) ? gloss : [gloss])
console.log(glossEntries);
/**
* Now all gloss items are collected in a single array and we can check if the id is found in any of the gloss strings.
*/
const isFound = glossEntries.includes(req.params.id)
console.log(`${req.params.id} in gloss values?`, isFound);
The person that posted an answer earlier gave me some clues, but he deleted it.
About the answer, the initial state of the logic was already too messy, and it was unable to return the true or false that the first 'filter' needed, that was the main problem.
So, i just started over focusing on the 'return' part and was there that things got better, anymore than that is just improvements to the code.
if ('sense' in x && !Array.isArray(x['sense'])) {
if (Array.isArray(x.sense['gloss'])) {
return x.sense.gloss.some(el => typeof(el) == 'string' && el.includes(req.params.id))
} else return typeof(x.sense.gloss) == 'string' && x.sense.gloss.includes(req.params.id)
} else if ('sense' in x && Array.isArray(x['sense'])) {
return x.sense.some((sense_item) => {
if (Array.isArray(sense_item['gloss'])) {
return sense_item.gloss.some(el => typeof(el) == 'string' && el.includes(req.params.id))
} else return typeof(sense_item.gloss) == 'string' && sense_item.gloss.includes(req.params.id)
})
}

JavaScript - Properly Extract Deep Object Properties and Construct New Object

Suppose the following array of objects is returned from an API:
const data = [
{ // first item
meta: {
stems: [
"serpentine",
"serpentinely"
]
},
hwi: {
hw: "sep*pen*tine",
prs: [
{
mw: "ˈsər-pən-ˌtēn",
sound: {
audio: "serpen02"
}
},
]
},
shortdef: [
"of or resembling a serpent (as in form or movement)",
"subtly wily or tempting",
"winding or turning one way and another"
]
},
{ // second item
meta: {
stems: [
"moribund",
"moribundities",
"moribundity"
]
},
hwi: {
hw: "mor*i*bund",
},
fl: "adjective"
}
]
I want to create a function that will generate a new array of objects. The objects in this new array will consist of data from the old objects, just rearranged. This is how I expect a new array to look, for example:
[
{
word: 'serpentine',
definitions: [
'of or resembling a serpent (as in form or movement)',
'subtly wily or tempting',
'winding or turning one way and another'
]
},
{
word: 'moribund',
definitions: [
'being in the state of dying : approaching death',
'being in a state of inactivity or obsolescence'
],
partOfSpeech: 'adjective'
}
]
I do this with the following function:
const buildNewData = arr => {
const newData = []
arr.forEach(item => {
newData.push({
...item.meta.stems[0] && { word: item.meta.stems[0]},
...item.shortdef && { definitions: item.shortdef },
...item.fl && { partOfSpeech: item.fl },
...item.hwi.prs[0].mw && { pronunciation: item.hwi.prs[0].mw}
})
})
return newData
}
buildNewData(data)
You may be curious as to why I use ...item.meta.stems[0] && { word: item.meta.stems[0]} in the creation of the new objects. This is to check if the property exists in the original object. If it doesn't exist, the expression will evaluate to false and therefore not be added to the new object. The first object in the original array does not have the fl property, so it evaluates to false when the new object is being constructed.
But this doesn't work when looking up a property that is an array. The code above fails with the error: TypeError: Cannot read property '0' of undefined. That's because the second item does not have a prs array under the hwi property, so the lookup fails.
Since I cannot control what data is returned from the API, how do I write a function that successfully creates a new array of objects in the format I've specified, without causing an error? I already have a solution to not add particular properties if they do not exist, but how do I take into account arrays?
More generally, I'm curious if there is a standardized way of extracting data from objects programmatically that prevents errors like this from occurring. Is there a better way to do this?
You need an additional guard so:
...item.hwi.prs[0].mw && { pronunciation: item.hwi.prs[0].mw}
becomes
...(Array.isArray(item.hwi.prs) && item.hwi.prs[0].mw) && { pronunciation: item.hwi.prs[0].mw}
which can be shortened to:
...(item.hwi.prs && item.hwi.prs[0].mw) && { pronunciation: item.hwi.prs[0].mw}
if you are confident that if item.hwi.prs exists its value will be an array that has a 0 value that can be spread.
const data = [
{ // first item
meta: {
stems: [
"serpentine",
"serpentinely"
]
},
hwi: {
hw: "sep*pen*tine",
prs: [
{
mw: "ˈsər-pən-ˌtēn",
sound: {
audio: "serpen02"
}
},
]
},
shortdef: [
"of or resembling a serpent (as in form or movement)",
"subtly wily or tempting",
"winding or turning one way and another"
]
},
{ // second item
meta: {
stems: [
"moribund",
"moribundities",
"moribundity"
]
},
hwi: {
hw: "mor*i*bund",
},
fl: "adjective"
}
];
const buildNewData = arr => {
const newData = []
arr.forEach(item => {
newData.push({
...item.meta.stems[0] && { word: item.meta.stems[0]},
...item.shortdef && { definitions: item.shortdef },
...item.fl && { partOfSpeech: item.fl },
...(Array.isArray(item.hwi.prs) && item.hwi.prs[0].mw) && { pronunciation: item.hwi.prs[0].mw}
})
})
return newData
}
let newData = buildNewData(data);
console.log(newData);
As you need to check existence of properties in an Object:
Use Optionnal chaining: https://javascript.info/optional-chaining
It returns a type undefined if the prop doesn't exist (but not string "undefined" ;) )
For desired order in new array, add numbers before the names of props.
let newData = [];
for (let i = 0; i < data.length; i++) {
newData[i] = {};
if (data[i]?.meta?.stems[i] != undefined)
newData[i].word = data[i].meta.stems[i];
if (data[i]?.shortdef != undefined) {
newData[i].definitions = data[i].shortdef.join(', ') + '.';
newData[i].definitions = newData[i].definitions.charAt(0).toUpperCase() + newData[i].definitions.substring(1); // Capitalize first letter
}
if (data[i]?.fl != undefined)
newData[i].partOfSpeech = data[i].fl;
}
console.log(...newData);

Group list of objects by JSON key

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!

update deep nested array in redux

I've been looking around to find a solution or a guide to cover my problem..
I need to find the right subsubdoc by an ID i'm passing with the action, and then the right subsubsubdoc by an ID that i'm also passing with the action.
I'm fetching the object from an API and then putting in the state.
The object I have simular to this:
proj = {
id:"zf123ada123ad",
name:"examp",
subdoc:{
name:"subdoc examp",
subsubdoc:[{
id:"zcgsdf123zaar21",
subsubsubdoc:[{
id:"af2317bh123",
value: "heyhey" //this value I want to update
}]
}]
}
}
in my reducer i have something like this atm:
I know this don't work because I don't get the specific object in the array, but that's what i don't know how to do. I have the id of the subsubsubdoc that i want to change value of.
export function answerUpdate(state = [], action){
switch(action.type){
case 'ANSWER_UPDATE_FETCH_SUCCESS':
return {
...state,
proj: {
...state.proj,
subdoc: {
...state.proj.subdoc,
subsubdoc: {
...state.proj.subdoc.subsubdoc,
subsubsubdoc: {
...state.proj.subdoc.subsubdoc.subsubsubdoc,
value: "hoy"
}
}
}
}
default:
return state
}
}
What I would like is this but a working and acceptable code inside the reducer:
state.doc.subdoc.subsubdoc.where(x => x.id ==theInputId1)
.subsubsubdoc.where(
x => x.id == theInputId2).value = theInputValue
Very grateful for every answer!!
Assuming action.payload is your Id, you should do something like
case 'ANSWER_UPDATE_FETCH_SUCCESS': {
const index = state.proj.subdoc.subsubdoc.findIndex(({id}) => id === action.payload);
const updatedObject = {
...state.proj.subdoc.subsubdoc[index],
value: newValue
}
return {
...state,
proj: {
...state.proj,
subdoc: {
...state.proj.subdoc,
subsubdoc: [
...state.proj.subdoc.subsubdoc.slice(0, index),
updatedObject,
...state.proj.subdoc.subsubdoc.slice(index)
]
}
}
}
}
you can have additional checks for when id is not present, simplify by reducing the usage of state.proj.subdoc.subsubdoc by storing it in a variable.
It is advised not to have such deep nesting in redux store.
My first concern would be to get the data into a regular format. If you have no control over the API, try making the format yourself once you receive it. This will save you alot of complex code. Smart data structures and dumb program code usually performs better than louzy data structures and tons of complex code.
var project = [
{ "id": "zf123ada123ad",
"name": "examp",
"value": null,
"docs": [
{ "id": "zcgsdf123zaar21",
"name": "examp-sub",
"value": null,
"docs": [
{ "id": "af2317bh123",
"name": "examp-sub-sub",
"value": "heyhey",
"docs": []
}
]
}
]
}
];
// 1st option: Recusively search.
var search = function( id ) {
var search_level = function( result, next ) {
if ( result ) return result;
else {
if ( next.id === id ) return next;
else return next.docs.reduce( search_level, null );
}
};
return search_level;
};
var heyhey_search = project.reduce( search( "af2317bh123" ), null );
console.log( 'option 1:' );
console.log( heyhey_search );
// 2nd option: Make a hash table. This is preferred if you need to access records by id often.
var flatten_hash = function( result, next ) {
// Own.
result[ next.id ] = next;
// Nested docs.
return next.docs.reduce( flatten_hash, result );
};
var project_hash = project.reduce( flatten_hash, {} );
var heyhey_hash = project_hash[ "af2317bh123" ];
console.log( 'option 2:' );
console.log( heyhey_hash );
var state = [
{
"id": 1,
"docs": [
{
"id": 3,
"value": "old value"
}
]
},
{
"id": 2,
"docs": []
}
];
console.log( 'old state' );
console.log( JSON.stringify( state ) );
var object_3 = state[0].docs[0];
object_3.value = "new value";
console.log( 'new state' );
console.log( JSON.stringify( 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