I have a JSON object that changes as it is appended with other objects throughout my application.
I have created a JavaScript function which behaves like indexOf but for a JSON array.
It looks like this:
// Find via slug
self.findSlug = function (array, slug) {
// Counter
var i = array.length;
// While i
while (i--) {
// If we find our slug, break the loop
if (array[i].slug === slug)
break;
}
// Return our counter
return i;
}
this works fine if the JSON object has a key called "slug". Now I would like to make it abiguous. i.e. something like:
if (array[i]["key-name"] === slug) break;
Here is an example of an array:
[
{
"title": "Hoody",
"price": 10,
"designable": true,
"configurable": true,
"slug": "hoody",
"fabric": "980506857489564534",
"font": "city-bold",
"fabrics": [
]
}
]
but that doesn't appear to work. Can someone suggest a way of doing this?
Cheers,
/r3plica
Please try this:
var array = {
1:{"title": "Hoody", "price": 10,"designable": true,"configurable": true,"slug": "hoody", "fabric": "980506857489564534",
"font": "city-bold", "fabrics": [ ] },
2:{"title": "Hoody2", "price": 20,"designable": true,"configurable": true,"slug": "hoody2", "fabric": "980506857489564534",
"font": "city-bold", "fabrics": [ ] }
}
for (key in array){
for (subkey in array[key]) {
if(subkey=="slug"){
//do something
}
}
}
Related
Assume I have the following object:
var jsonObj = {
"response":{
"result":{
"status":{
"name": "Eric"
}
}
}
}
And now i'd like to dynamically access a nested property:
jsonKey = "response.result.status.name";
console.log("the status is: " + jsonObj.jsonKey); //I cannot call jsonObj.jsonKey here
Is there any way to achieve this?
You cannot access a deeply nested property as simple as you expect. Instead you need to use the obj[propertyNameAsString] syntax to dive deeper into the response one by one.
This would be one way of getting there:
let response = {
"response": {
"method": "GetStatus",
"module": "Module",
"data": null,
"result": {
"status": {
"name": "Eric"
},
"id": 1
},
"result_code": {
"error_code": 0
}
}
}
let keyString = "response.result.status.name"
let keyArray = keyString.split('.'); // [ "response", "result", "status", "name" ]
var result = response;
for (key of keyArray) {
result = result[key]
}
console.log(result)
Please be aware that this is not failsafe against cases where one of those strings in keyArray does not exist as a property on the preceding object.
You can do like this something['bar']
Where bar is your variable that has been converted to string, in our case:
jsonObj[`${jsonKey}`]
I'm using Vue, lodash, etc.
{
"street": {
"id": "1",
"streetName": "test",
"buildings": [
{
"id": "1",
"buildingName": "test"
}
]
}
}
I have a setup similar to this. This is a singular object, I basically have an array of these.
All I get is a building.id value.
From it, I need to be able to find the building it belongs to, and there isn't any direct list of buildings for me to access.
Currently
I'm using a nested loop to loop through each site until I find the one that has a building with that id. I don't know if I'm doing it correctly, it doesn't feel correct.
for(var i = 0; i < streets.length; i++){
for(var x = 0; x < streets[i].buildings.length;x++){
if(streets[i].buildings[x].id == '2aec6bed-8cdd-4043-9041-3db4681c6d08'){
}
}
}
Any tips? Thanks.
You can use a combination of filter and some methods, like this:
var result = streets.filter(function(s) {
return s.street.buildings.some(function(b) {
return b.id === searchedId;
});
});
Using .some() method will return true if any building of the iterated buildings has the searchedId.
Using .filter() will filter the streets array to return only street object where the call of some() method on its buildings will return true, in other words which meets the condition of having an idequal to searchedId.
Demo:
var streets = [{
"street": {
"id": "1",
"streetName": "test",
"buildings": [{
"id": "1",
"buildingName": "test"
}]
}
}, {
"street": {
"id": "1",
"streetName": "test",
"buildings": [{
"id": '2aec6bed-8cdd-4043-9041-3db4681c6d08',
"buildingName": "test"
}]
}
}];
var searchedId = '2aec6bed-8cdd-4043-9041-3db4681c6d08';
var result = streets.filter(function(s) {
return s.street.buildings.some(function(b) {
return b.id === searchedId;
});
});
console.log(result);
If you're trying to get all the buildings in all streets by a buildingId, this solves the problem:
streetsList.map(streetItem => streetItem.street.buildings.find(building => building.id === searchedBuildingId)).filter(v => v);
.filter(v => v) is for filtering out falsy values since we want a clean result here.
If there can be more than a single building in a street with the same id, then use .some instead of .find in the example.
Presumably you have a streets object that contains street objects, like:
var streets = [
street :{ ... },
street :{ ... },
...
];
So you need to step into each street and iterate over the buildings. A for loop should be fairly efficient since it can return as soon as it finds the building. I don't think any of the built-in looping methods will do that.
The code in the OP won't work, as streets[i].buildings must be streets[i].streets.buildings and if(streets[i].buildings[x].id must be if(streets[i].street.buildings[x].id.
Below is a working for loop version, there's also a version using recent Array methods which are very much slower even on a very small dataset. According to jsperf, the for loop version is about 100 times faster in Safari, 10 times faster in Firefox and 50 times faster in Chrome.
I also think the for loop code is much more readable and therefore maintainable.
var streets = [{
"street": {
"id": "1",
"streetName": "test",
"buildings": [{
"id": "1",
"buildingName": "test"
}, {
"id": "2",
"buildingName": "test"
}]
}
}, {
"street": {
"id": "2",
"streetName": "test",
"buildings": [{
"id": "3",
"buildingName": "test"
}]
}
}
];
function getBldById(data, id) {
for (var i=0, iLen=streets.length; i<iLen; i++) {
var street = streets[i].street;
for (var j=0, jLen=street.buildings.length; j<jLen; j++) {
if (street.buildings[j].id == id) {
return street.buildings[j];
}
}
}
return null;
}
console.log(getBldById(streets, '1'));
function getBldById2(data, id) {
return data.map(streetObj =>
streetObj.street.buildings.find(building =>
building.id === id)
).filter(v => v)[0];
}
console.log(getBldById2(streets, '1'));
You might be missing street property, right?
I mean it should be: streets[i].street.buildings[x].id
I have a response from a web service and want to replace some values in the response with my custom values.
One way is to write a tree traverser and then check for the value and replace with my custom value
so the response is some what like this:
[
{
"name": "n1",
"value": "v1",
"children": [
{
"name": "n2",
"value": "v2"
}
]
},
{
"name": "n3",
"value": "v3"
}
]
now my custom map is like this
const map = {
"v1": "v11",
"v2": "v22",
"v3": "v33"
};
All I want is
[
{
"name": "n1",
"value": "v11",
"children": [
{
"name": "n2",
"value": "v22"
}
]
},
{
"name": "n3",
"value": "v33"
}
]
I was thinking if I could stringify my response and then replace values using a custom build regex from my map of values.
Will it be faster as compared to tree traverser?
If yes, how should I do that?
somewhat like this
originalString.replace(regexp, function (replacement))
The tree traversal is faster
Note that some things could be done more efficiently in the regex implementation but I still think there are some more bottlenecks to explain.
Why the regex is slow:
There are probably many more reasons why the regex is slower but I'll explain at least one significant reason:
When you're using regex to find and replace, you're using creating new strings every time and performing your matches every time. Regex expressions can be very expensive and my implementation isn't particularly cheap.
Why is the tree traversal faster:
In the tree traversal, I'm mutating the object directly. This doesn't require creating new string objects or any new objects at all. We're also not performing a full search on the whole string every time as well.
RESULTS
run the performance test below. The test using console.time to record how long it takes. See the the tree traversal is much faster.
function usingRegex(obj, map) {
return JSON.parse(Object.keys(map).map(oldValue => ({
oldValue,
newValue: map[oldValue]
})).reduce((json, {
oldValue,
newValue
}) => {
return json.replace(
new RegExp(`"value":"(${oldValue})"`),
() => `"value":"${newValue}"`
);
}, JSON.stringify(obj)));
}
function usingTree(obj, map) {
function traverse(children) {
for (let item of children) {
if (item && item.value) {
// get a value from a JS object is O(1)!
item.value = map[item.value];
}
if (item && item.children) {
traverse(item.children)
}
}
}
traverse(obj);
return obj; // mutates
}
const obj = JSON.parse(`[
{
"name": "n1",
"value": "v1",
"children": [
{
"name": "n2",
"value": "v2"
}
]
},
{
"name": "n3",
"value": "v3"
}
]`);
const map = {
"v1": "v11",
"v2": "v22",
"v3": "v33"
};
// show that each function is working first
console.log('== TEST THE FUNCTIONS ==');
console.log('usingRegex', usingRegex(obj, map));
console.log('usingTree', usingTree(obj, map));
const iterations = 10000; // ten thousand
console.log('== DO 10000 ITERATIONS ==');
console.time('regex implementation');
for (let i = 0; i < iterations; i += 1) {
usingRegex(obj, map);
}
console.timeEnd('regex implementation');
console.time('tree implementation');
for (let i = 0; i < iterations; i += 1) {
usingTree(obj, map);
}
console.timeEnd('tree implementation');
Will it be faster as compared to tree traverser?
I don't know. I think it would depend on the size of the input, and the size of the replacement map. You could run some tests at JSPerf.com.
If yes, how should I do that?
It's fairly easy to do with a regex-based string replacement if the values you are replacing don't need any special escaping or whatever. Something like this:
const input = [
{
"name": "n1",
"value": "v1",
"children": [
{
"name": "n2",
"value": "v2"
}
]
},
{
"name": "n3",
"value": "v3"
}
];
const map = {
"v1": "v11",
"v2": "v22",
"v3": "v33"
};
// create a regex that matches any of the map keys, adding ':' and quotes
// to be sure to match whole property values and not property names
const regex = new RegExp(':\\s*"(' + Object.keys(map).join('|') + ')"', 'g');
// NOTE: if you've received this data as JSON then do the replacement
// *before* parsing it, don't parse it then restringify it then reparse it.
const json = JSON.stringify(input);
const result = JSON.parse(
json.replace(regex, function(m, key) { return ': "' + map[key] + '"'; })
);
console.log(result);
definitely traverser go faster as string replace means travels against each characters in the final string as opposed to iterator that can skips no necessarily item.
I have the following JSON:
{
"responseObject": {
"name": "ObjectName",
"fields": [
{
"fieldName": "refId",
"value": "2170gga35511"
},
{
"fieldName": "telNum",
"value": "4541885881"
}]}
}
I want to access "value" of the the array element with "fieldName": "telNum" without using index numbers, because I don't know everytime exactly at which place this telNum element will appear.
What I dream of is something like this:
jsonVarName.responseObject.fields['fieldname'='telNum'].value
Is this even possible in JavaScript?
You can do it like this
var k={
"responseObject": {
"name": "ObjectName",
"fields": [
{
"fieldName": "refId",
"value": "2170gga35511"
},
{
"fieldName": "telNum",
"value": "4541885881"
}]
}};
value1=k.responseObject.fields.find(
function(i)
{return (i.fieldName=="telNum")}).value;
console.log(value1);
There is JSONPath that lets you write queries just like XPATH does for XML.
$.store.book[*].author the authors of all books in the store
$..author all authors
$.store.* all things in store, which are some books and a red bicycle.
$.store..price the price of everything in the store.
$..book[2] the third book
$..book[(#.length-1)]
$..book[-1:] the last book in order.
$..book[0,1]
$..book[:2] the first two books
$..book[?(#.isbn)] filter all books with isbn number
$..book[?(#.price<10)] filter all books cheapier than 10
$..* All members of JSON structure.
You will have to loop through and find it.
var json = {
"responseObject": {
"name": "ObjectName",
"fields": [
{
"fieldName": "refId",
"value": "2170gga35511"
},
{
"fieldName": "telNum",
"value": "4541885881"
}]
};
function getValueForFieldName(fieldName){
for(var i=0;i<json.fields.length;i++){
if(json.fields[i].fieldName == fieldName){
return json.fields[i].value;
}
}
return false;
}
console.log(getValueForFieldName("telNum"));
It might be a better option to modify the array into object with fieldName as keys once to avoid using .find over and over again.
fields = Object.assign({}, ...fields.map(field => {
const newField = {};
newField[field.fieldName] = field.value;
return newField;
}
It's not possible.. Native JavaScript has nothing similar to XPATH like in xml to iterate through JSON. You have to loop or use Array.prototype.find() as stated in comments.
It's experimental and supported only Chrome 45+, Safari 7.1+, FF 25+. No IE.
Example can be found here
Clean and easy way to just loop through array.
var json = {
"responseObject": {
"name": "ObjectName",
"fields": [
{
"fieldName": "refId",
"value": "2170gga35511"
},
{
"fieldName": "telNum",
"value": "4541885881"
}]
}
$(json.responseObject.fields).each(function (i, field) {
if (field.fieldName === "telNum") {
return field.value // break each
}
})
I'm working with a response from the Webtrends API in Google apps script and I have a JSON/JS object that looks like this:
"data": [
{
"period": "Month",
"start_date": "2013-12",
"end_date": "2013-12",
"attributes": {},
"measures": {
"Visits": 500
},
"SubRows": [
{
"facebook.com": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"google.co.uk": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"newsnow.co.uk": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"No Referrer": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"t.co": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
}
}
]
}
]
What I need to access is the names i.e facebook.com etc... and visit numbers for each of the SubRows.
I'm able to get the visit numbers, but I can't work out how to get the names. Please note the names will change constantly as different sites will send different amounts of traffic each day.
Section of my code at the moment where I get the visit numbers:
for(i in dObj){
var data = dObj[i].SubRows;
var sd = dObj[i].start_date;
var ed = dObj[i].end_date;
if(sd == ed){
var timep = ""+ sd;
}
else{
var timep = ""+ sd + "-" + ed;
}
var subRows = data[0];
Logger.log(subRows);
for(i in subRows){
var row = subRows[i];
var rmeasures = row.measures;
var rvis = rmeasures.Visits;
values = [timep,"",rvis]; //Blank string for where the name of the site would go
}
}
I've tried the following links, but none of them seem to have the answer:
Getting JavaScript object key list
How to access object using dynamic key?
How to access key itself using javascript
How do I access properties of a javascript object if I don't know the names?
I'm just using vanilla google apps script as I don't have any experience with Jquery etc...
Any help would be much appreciated!
I usually use a little helper function that looks like this:
var keyVal = function(o) {
var key = Object.keys(o)[0];
return {"key": key, "val":o[key]};
} ;
This will map an object with a variable key to a key/value object {key:...., val:{}}, which is usually convenient enough to work with.
describe.only ("stack overflow answer", function(){
it ("is should create a key/value pair" , function(){
var res = keyVal( {
"facebook.com": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
}});
res.key.should.equal('facebook.com');
res.val.attributes.should.deep.equal({});
});
Within the loop, the variable i contains the current key. Replacing the empty string with i should give you what you need.
You might also want to look at some of the more functional tools built into Javascript. Some more concise code might also be more explicit:
data.map(function(datum) {
var timep = datum.start_date == datum.end_date ? datum.end_date :
(data.start_date + "-" + datum.end_date);
return datum.SubRows.map(function(subRow) {
return Object.keys(subRow).map(function(key) {
return [timep, key, subRow[key].measures.Visits];
});
});
});
would return an object something like this:
[
[
[
["2013-12", "facebook.com", 100],
["2013-12", "google.co.uk", 100],
["2013-12", "newsnow.co.uk", 100],
["2013-12", "No Referrer", 100],
["2013-12", "t.co", 100 ]
]
]
]
This just uses map and Object.keys to simplify some of what you're doing with explicit loops.