Can't access data inside an object - rookie Javascript question - javascript

This is a rookie question about accessing an array inside an object and converting it to JSON.
Error I am getting when running the GET method below:
Cannot read property 'VehicleList' of undefined
How to fix this?
let get = https.get(
{
hostname: 'xyz.com',
path: path_get_all_vehicles //this variable is known and passed into path
},
(getRes) => {
console.log(`executing getRes...`);
var reply = "";
getRes.on("data", (chunk) => {
(reply += chunk);
}
);
getRes.on("end", () => {
gotCars(JSON.parse.stringify(reply.Response.VehicleList)); //ERROR
}
);
}
);
Data format is shown below and the idea is to access the VehicleList array, convert it to JSON and then pass it, after parsing, to function gotCars
{
"ResponseStatus": 1,
"Response": {
"VehicleList": [
{
"ID": AAA,
"BrandID": 89,
"ModelID": 980,
"VersionID": 11289
},
{
"ID": BBB,
"BrandID": 89,
"ModelID": 980,
"VersionID": 8338
},
],
"VehicleCount": 17866
}
}

The expression JSON.parse.stringify(reply.Response.VehicleList) is invalid for a couple reasons:
The global JSON.parse does not have a property named stringify (undefined)
undefined cannot be called
The global String.prototype (reply is a string) does not have a property named Response (undefined)
undefined cannot be indexed
I assume you are trying to parse reply as JSON and then get the VehicleList array from the result, try this code:
let get = https.get(
{
hostname: "xyz.com",
path: path_get_all_vehicles // this variable is known and passed into path
},
(getRes) => {
console.log(`executing getRes...`);
var reply = "";
getRes.on("data", (chunk) => {
reply += chunk;
});
getRes.on("end", () => {
gotCars(JSON.parse(reply).Response.VehicleList);
});
}
);

You first need to parse reply, that will create the object. Then you can access its properties.
getRes.on("end", () => {
const obj = JSON.parse(reply);
gotCars(obj.Response.VehicleList);
});

Related

Sinon stub - mocking a function which returns an array of objects

I am trying to stub the following code
async function logUpdate(client) {
const results = await client.query(query.toParam());
const { count, timestamp } = results.rows[0];
await db.updateDatasourceLogs(destdb, DB.src, TABLES.src, timestamp, count);
}
This is the following code i am using to stub the above code
fakeClient = {
query: sinon.stub().resolves(fakeRows),
};
const rowData = {
count: 1,
timestamp: ''
};
fakeRows = {
rows: sinon.stub().returns([rowData]),
};
fakeSequel = {
useFlavour: sinon.stub().returns(toParam: () => false,),
};
I am getting an error for destructuring
TypeError: Cannot destructure property count of 'undefined' or 'null'.
at line
const { count, timestamp } = results.rows[0];
how to stub the above line?
If we look at the body of your logUpdate function, we see it begins with the following two lines:
const results = await client.query(query.toParam());
const { count, timestamp } = results.rows[0];
This code says:
Await the Promise returned by the call to client.query and assign
it to a variable called results.
results is an Object with a property called rows which is an
Array whose 0th element should be an Object with count and
timestamp properties - which we destructure into local variables.
This implies that the value of results looks like:
{
"rows": [
{
"count": 1
"timestamp": "0"
}
]
}
However, in our stubbing code, we have the following:
fakeRows = {
rows: sinon.stub().returns([rowData]),
};
Which says that fakeRows is an Object with a rows property whose value is a function that returns [rowData].
If we were to implement this Object without sinon, it would look like:
{
"rows": function () {
return [
{
"count": 1
"timestamp": "0"
}
];
}
}
Notice the difference between the structure that logUpdate is expecting and what fakeRows actually provides. Specifically, logUpdate expects results to have a rows Array, but, with our stubbed code, it has a rows Function!
We can fix this simply by having fakeRows reference our rowData directly:
const fakeRows = {
rows: [rowData]
};

How to get specific attribute inside a tag of javascript object?

So I have this XML :
<enclosure length="25000" type="image/jpeg" url="https://www.hellothere.com/something.jpg"/>
and I have already converted it to a javascript object like this :
{
title: (1) ["something"],
enclosure: (1) [{...}]
}
I already watched it while debugging and the value is there, while it is undefined, everytime I try to access it.
When I do console.log(JSON.stringify(data, null, 2)) it return this result :
"enclosure": [
{
"$": {
"length": "25000",
"type": "image/jpeg",
"url": "https://hellothere.com/something.jpg"
}
}
]
but when I do console.log(data) , it returns undefined
if we stretch it :
data :
> title: (1) ['something here']
> enclosure: (1) [{...}]
> 0: {$: {...}}
> $: {length='25000',type='image/jpeg',url='https://www.hellothere.com/something.jpg'}
length: '25000'
type: 'image/jpeg'
url: 'https://www.hellothere.com/something.jpg'
In python, I tried using something like data['enclosure']['#url'] and it works.
I've tried :
> data['enclosure'][0]['url']
> data['enclosure']['#url']
> data['enclosure']['$url']
but nothing worked.
How do I get the value of url in that enclosure tag ? Thank you
EDIT :
codes :
index.js
app.get('/', (req, res) => {
getJson.produce_data(url_source.url, function (err, data) {
data = data["rss"]["channel"][0]["item"];
if (err) {
return console.error(err);
}
res.render('json_results', {title: 'NEWS TODAY', data_in: data});
});
});
produce_data function :
function produce_data(url, callback) {
var req = https.get(url, function(res) {
var currentData = '';
res.on('data', function(chunk) {
currentData += chunk;
})
res.on('error', function(err) {
callback(err, null);
})
res.on('timeout', function(err) {
callback(err, null);
})
res.on('end', function() {
parsedtext(currentData , function(err, result) {
callback(null, result);
})
});
});
}
These are the lines of code that involved in the process.
Screenshot :
This is the data contained when in debug mode :
Reconstructing the object from what looks like a console inspection and applying the following rules:
objects props are referenced by dot and the prop name
array elements are referenced by square braces and the index
so...
let data = {
title: ['something here'],
enclosure: [{
$: {
length: '25000',
type: 'image/jpeg',
url: 'https://www.hellothere.com/something.jpg'
}
}]
}
console.log(data.enclosure[0].$.url)
Your enclosure property is an array so you have to refer to it by index:
let xml_obj = {
title: ["something"],
enclosure: [{
length:'25000',type:'mage/jpeg',url:'https://www.hellothere.com/something.jpg'
}]
}
//in this case there is one element so the index is zero!
alert(xml_obj.enclosure[0].url)
This is in JavaScript, you seem to be using both JS and Python in your project.
The OP doesn't contain enough actual code to determine what you're doing, so the following is something of a guess.
You can turn the XML into a document, then access the nodes and attributes using DOM methods, e.g.
let xml = '<enclosure length="25000" type="image/jpeg" url="https://www.hellothere.com/something.jpg"/>';
let doc = new DOMParser().parseFromString(xml, 'application/xml');
let enclosures = doc.getElementsByTagName('enclosure');
for (let i=0, iLen=enclosures.length; i<iLen; i++) {
console.log(`Node ${i} url: ${enclosures[i].getAttribute('url')}`);
}
Ok this question is closed. I've found it.
Turns out I only have to add '$' because this symbol -> $ somehow behaves like a pointer (?). I didn't modify the content of XML and the $ is added itself.
So it's not a string.
If it's a string, the solutions above and provided has already solved it , but it doesn't.
Since my data is in an array, this line of code works :
data[sequence]['enclosure'][0]['\$']['url']

How to get JSON data with changing part of the call to get that data

I'm working on a side project of a currency converter. I've made an API Call and this is all the data it sent me (this varies depending on the selected currencies so USD_GBP could be CAD_JPY):
{
"query": {
"count": 1
},
"results": {
"USD_GBP": {
"id": "USD_GBP",
"val": 0.733695,
"to": "GBP",
"fr": "USD"
}
}
}
In my express code I've set up the HTTPS Module:
https.get(url, function(response) {
response.on("data", function(data) {
const currencyData = JSON.parse(data);
const exchangeRate = currencyData.results.USD_GBP.val;
});
});
});
The problem you can probably see is that since the currency to convert from and convert to are always changing the constant exchangeRate doesn't work for any scenario besides converting from USD to GBP. So I'm wondering how to make that work for every combination that I throw at it. Above my HTTPS get request I already made a variable (const combine = firstCurrency + "_" + secondCurrency;) that combines the selections. I tried concatenating it to the exchangeRate variable, but it gave me an error that it can't find the value of undefined.
JavaScript objects allow you to use bracket notation for dynamic variables names.
var obj = { bar: 'baz'; }
var foo = 'bar';
obj.foo ; // This is invalid, as it looks for the "foo" property
obj[foo]; // This is valid, as it looks for the "bar" property we want
Since you mentioned you have a variable named combine already, you should be able to use bracket notation on the results:
const exchangeRate = currencyData.results[combine].val;
Here's a quick little snippet:
var firstCurrency = 'USD';
var secondCurrency = 'GBP';
const combine = firstCurrency + "_" + secondCurrency;
var response = `{
"query": {
"count": 1
},
"results": {
"USD_GBP": {
"id": "USD_GBP",
"val": 0.733695,
"to": "GBP",
"fr": "USD"
}
}
}`;
const currencyData = JSON.parse(response);
const exchangeRate = currencyData.results[combine].val;
console.log( exchangeRate );
If you didn't have the combine variable, you could always use Object.keys() to get the keys from the result and use that, but it's arguably uglier (especially since you already know the key):
const exchangeRate = currencyData.results[Object.keys(currencyData.results)[0]].val;
Instead of currencyData.results.USD_GBP.val, use currencyData.results['USD_GBP'].val
You can even use a variable as well. :
currencies = ['USD_GBP', 'EUR_USD']
currenceis.forEach(currency => {
currencyData.results[currency].val
}
Changed Example:
https.get(url, function(response) {
response.on("data", function(data) {
const currencyData = JSON.parse(data);
const exchangeRate = currencyData.results['USD_GBP'].val;
});
});
});
You can use the backtick syntax.
let firstCurrency = "USD";
let secondCurrency = "GBP";
const currencyData = {
"query": {
"count": 1
},
"results": {
"USD_GBP": {
"id": "USD_GBP",
"val": 0.733695,
"to": "GBP",
"fr": "USD"
}
}
};
const exchangeRate = currencyData.results[`${firstCurrency}_${secondCurrency}`].val;
console.log(exchangeRate);

Setting the property of one specific key in an object in a reduce is setting it for all properties

Okay, I have literally no idea whats going on here. I'm assuming its some kind of reference issue? But I dont know how to get around it or whats causing it.
To sum it up, I have a list of objects, as well as an object that gets prepopulated to make sure I have data for all keys in the object.
I need to iterate over this list of objects, and by using the timeframeId in the metadata object, and the id in the data object, I want to assign the entire data object to the corresponding timeframeId and id hierarchy in the prepopulated object.
For some reason, all data properties are being overwritten to whatever the last row data is.
I've linked a repl so you can see for yourself: https://repl.it/#ThomasVermeers1/UnwrittenNoisyFirm#index.js
But my code is as follows:
const buildSegmentsFromRows = (rows, timeframeMetadata, defaultSegmentData) => {
// Prepopulate object to make sure every timeframe has a 'hello' key in it with some data
const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
segmentMap[metadata.timeframeId] = {
metadata,
segments: defaultSegmentData,
};
return segmentMap;
}, {});
// Now simply just loop over the rows, and set [row.metadata.timeframeId].segments[row.data.id] to row.data
const segments = rows.reduce((partialSegments, row) => {
const { timeframeId } = row.metadata;
const { id } = row.data;
/**
* This is the line where everything goes wrong
*/
partialSegments[timeframeId].segments[id] = row.data;
return partialSegments;
}, emptySegments);
return segments;
};
const rows = [
{
metadata: { timeframeId: '20202_01' },
data: {
'id': 'hello', 'value': 15
}
},
{
metadata: { timeframeId: '20202_02' },
data: {
'id': 'hello', 'value': 10
}
}
]
const timeframemetadata = [
{ timeframeId: '20202_01'},
{ timeframeId: '20202_02'}
]
const defaultSegmentData = {
'hello': {
'id': 'hello',
}
}
console.log(JSON.stringify(buildSegmentsFromRows(rows, timeframemetadata, defaultSegmentData), null, 2))
I'm expecting the end result to be:
{
"20202_01": {
"metadata": {
"timeframeId": "20202_01"
},
"segments": {
"hello": {
"id": "hello",
"value": 15
}
}
},
"20202_02": {
"metadata": {
"timeframeId": "20202_02"
},
"segments": {
"hello": {
"id": "hello",
"value": 10
}
}
}
}
But instead, value is getting set to 10 in all instances. I'm thinking its because we're setting the property to row.data, which is a reference, and gets updated on every call? But I'm at a complete loss here.
The problem is that you are referring to the same object for every segments in the list.
Therefore, changing the value of segments[id] will update defaultSegmentData, causing every reference to defaultSegmentData to change as well.
const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
segmentMap[metadata.timeframeId] = {
metadata,
segments: defaultSegmentData, // Everything goes wrong here.
};
return segmentMap;
}, {});
A simple solution to this problem is to avoid using the same reference to the object when creating the segmentMap:
const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
segmentMap[metadata.timeframeId] = {
metadata,
/** Or whatever default value you want.
* Just make sure to create a new instance of it for each call.
*/
segments: {},
};
return segmentMap;
}, {});

MongoDB: Why is Array.find on comments (an array) property returning undefined?

Probably a silly issue, but why is the Array.find method not working as expected when working in this case? I'm trying to query a specific comment, which involves fetching the post document that has a comments property from the DB. It is from this comments array that I'd like to extract said comment object. For whatever reason, the code below doesn't work. Why?
Below are the code snippets
// Post document from which the comments are extracted
const post = await Post.findById(postId).populate({
path: "comments",
select: "addedBy id"
});
// Resulting post.comments array
[
{ "id": "5d9b137ff542a30f2c135556", "addedBy": "5b8528131719dc141cf95c99" },
{ "id": "5d9b0ba2f28afc5c3013d4df", "addedBy": "5b8528131719dc141cf95c99" },
{ "id": "5d9b0c26f28afc5c3013d4e0", "addedBy": "5b8528131719dc141cf95c99" }
];
// For instance if commentId is '5d9b137ff542a30f2c135556'
// the resulting comment object should be {"id":"5d9b137ff542a30f2c135556","addedBy":"5b8528131719dc141cf95c99"}
// However, commentToDelete is undefined
const commentId = "5d9b137ff542a30f2c135556";
const commentToDelete = comments.find(comment => comment["id"] === commentId);
Edit: Here's the full deleteComment controller code
async function deleteComment(req, res, userId, postId, commentId) {
const post = await Post.findById(postId).populate({
path: 'comments',
select: 'addedBy id',
});
const commentToDelete = post.comments.find(
comment => comment['id'] === commentId
);
if (commentToDelete.addedBy !== userId) {
return res
.status(403)
.json({ message: 'You are not allowed to delete this comment' });
}
await Comment.findByIdAndDelete(commentId);
const updatedPost = await Post.findByIdAndUpdate(
post.id,
{ $pull: { comments: { id: commentId } } },
{ new: true, safe: true, upsert: true }
).populate(populateFields);
return res.status(200).json({ updatedPost });
}
comment => comment['id'] === commentId
Your comment subdocument comes from MongoDB/Mongoose, so comment['id'] will likely be of type ObjectID, which is never equal a string. Explicitly call the toString() function (or use some other approach for transforming to a string) before comparing:
comment => comment['id'].toString() === commentId
works fine in the below snippet, copied from your post!
I am assuming it is posts.comments in your case and not comments.find? Check for typos
const comments = [
{ "id": "5d9b137ff542a30f2c135556", "addedBy": "5b8528131719dc141cf95c99" },
{ "id": "5d9b0ba2f28afc5c3013d4df", "addedBy": "5b8528131719dc141cf95c99" },
{ "id": "5d9b0c26f28afc5c3013d4e0", "addedBy": "5b8528131719dc141cf95c99" }
];
// For instance if commentId is '5d9b137ff542a30f2c135556'
// the resulting comment object should be {"id":"5d9b137ff542a30f2c135556","addedBy":"5b8528131719dc141cf95c99"}
const commentId = "5d9b137ff542a30f2c135556";
// However, commentToDelete is undefined
const commentToDelete = comments.find(comment => comment["id"] === commentId);
console.log(commentToDelete);
you can use this :
const result = comments.find(
({ id }) => id === commentId,
);
console.log(result)
// should return { id: '5d9b137ff542a30f2c135556', addedBy: '5b8528131719dc141cf95c99' }

Categories