Javascript array is not accessible as an array - javascript

I'm working with the twitter API and I'm hitting a really confusing issue.
I have the following script:
const Twitter = require('twitter-api-stream')
const twitterCredentials = require('./credentials').twitter
const twitterApi = new Twitter(twitterCredentials.consumerKey, twitterCredentials.consumerSecret, function(){
console.log(arguments)
})
twitterApi.getUsersTweets('everycolorbot', 1, twitterCredentials.accessToken, twitterCredentials.accessTokenSecret, (error, result) => {
if (error) {
console.error(error)
}
if (result) {
console.log(result) // outputs an array of json objects
console.log(result.length) //outputs 3506 for some reason (it's only an array of 1)
console.log(result[0]) // outputs a opening bracket ('[')
console.log(result[0].text) // outputs undefined
}
})
Which is calling the following function to interact with twitter:
TwitterApi.prototype.getUsersTweets = function (screenName, statusCount, userAccessToken, userRefreshToken,cb ) {
var count = statusCount || 10;
var screenName = screenName || "";
_oauth.get(
"https://api.twitter.com/1.1/statuses/user_timeline.json?count=" + count + "&screen_name=" + screenName
, userAccessToken
, userRefreshToken
, cb
);
};
It seems like I'm getting the result I want. When I log the result itself I get the following output:
[
{
"created_at": "Thu Sep 01 13:31:23 +0000 2016",
"id": 771339671632838656,
"id_str": "771339671632838656",
"text": "0xe07732",
"truncated": false,
...
}
]
Which is great, an array of the tweets limited to 1 tweet.
The problem I'm running into is when I try to access this array.
console.log(result.length) //outputs 3506 for some reason (it's only an array of 1)
console.log(result[0]) // outputs a opening bracket ('[')
console.log(result[0].text) // outputs undefined
I read back through the api docs for the user_timeline but unless I'm completely missing it I'm not seeing any mention of special output.
Any ideas?
Update
Thanks #nicematt for pointing out that answer.
Just to elaborate on the solution, I updated my code to this and now I'm getting the result I want:
if (result) {
let tweet = JSON.parse(result)[0] // parses the json and returns the first index
console.log(tweet.text) // outputs '0xe07732'
}
Thanks for the help!

Result is a String and you're indexing it (result[0] (whereas 0 is converted to a string), is almost identical to result.charAt(0) though), this is why result[0] is equal to "["–because it's the first character specified in. You forgot to parse the result as JSON data.
JSON.parse(result).length // probably 1
And result.text is undefined since result (a string) is like an Object (but isn't instanceof) and allow lookups and getters to happen in itself.
I'd show the difference between str[0] and str.charAt(0), too:
str[0] // same as str['0'], but a getter. 0 converts to
// string (because every key of an object
// is string in ECMAScript)
str.charAt(0) // get/lookup String#charAt, call it
// without new `this` context and with arguments list: 0

Related

Can't read properties in a JSON object in JS

I've been trying to work on this project to get better at coding for about half and a month now and I've come across a problem where JSON doesn't really behave the way I want it to. When I try reading a part of a JSON object, it almost always shows 'undefined' as a result.
This is the code, where I store my JSON into a cookie:
var basket = '{ "basket":['+'{ "id": 0, "data-id": 3, "amount": 1 }'+'] }';
document.cookie = 'basket='+JSON.parse(basket)+'; max-age="604800"; path=/';
I read it with:
var basket = getCookie('basket');
for(var i in basket) {
alert(basket[i]);
}
This is the last approach I've tried, this one returns the '{' (i.e. the first character of the JSON when I define it), meaning it behaves as if it were a string, right? In cases, where I've tried to read it with just alert(basket[0]) or alert(basket.basket[0].id) or anything (I've tried countless combinations) it almost always returns 'undefined' with the exception of only returning a part of a string.
Any ideas?
Declare basket object and while saving into cookie use JSON.stringify() method to save it; and when you extract it from cookie use JSON.parse() method to convert it back from string to object.
let basket = {basket:[{id: 0, dataId: 3, amount: 1 }] };
document.cookie = 'basket='+JSON.stringify(basket)+'; max-age="604800"; path=/';
function getBasketFromCookie() {
const basketValue = document.cookie
.split('; ')
.find(row => row.startsWith('basket='))
.split('=')[1];
const basketObj = JSON.parse(basketValue);
return basketObj;
}
const basketObj = getBasketFromCookie();
// print basket object
for (let key in basketObj) {
console.log(basket[key]);
}
console output:

JSON parse returns empty string?

I am not sure if this is an issue about VueJS or JS itself.
I have string in my DB (converted JS Object with JSON.stringify() ) which looks like this:
{"type":5,"values":{"7":"/data/images/structured-content/64-7-scico.jpg","8":"<b>wefwe</b>","9":"Nějaký text","10":"/data/images/structured-content/64-10-scico.jpg"}}
What I wanted to do is select it from database (via Axios), convert it back to JS object and set it to VueJS data:
.then(response => {
if (response.data.response === "ok" && response.status == 200) {
// get data
let data = response.data.data.data[0];
// pass name to state
this.objectName = data.name;
// get json in string format
let result = data.content;
// first log
console.log(result);
// convert string to json
let content = JSON.parse( result );
// second log
console.log( content.values );
// update states
this.IDType = content.type;
this.values = content.values;
}
})
Axios, data.name and content.type works fine, however the second log (content.values) seems to be returning observer with empty strings which I can't pass to Vue data and work with it, as you can see on the screen below, values in this object are just empty no matter what I do.
What is exactly wrong in here? Thank you!
from debugger:
To be reactive Vue needs to know what the keys of an object are before you assign them. So if they are known, pre assign them:
data : () => ({
values : {
7 : "",
8 : "",
9 : "",
10 : ""
}
}),
Then use object assign to set them:
Object.assign(this.values, content.values)
If the keys are unknown / dynamic values you can set them to be reactive using the $set method:
Object.keys(content.values).forEach(
function(key){
this.$set(this.value, key, content.values[key])
}
)

Why JSON.stringify($('#calendar').fullcalendar('clientEvents')) fails?

As title, when I try to do:
myString = JSON.stringify($('#calendar').fullcalendar('clientEvents'));
it fails. I tried to alert myString but I see a series of [Object object], ... . But if i try to alert myArray[0].title for example, it returns correctly.
Where I'm doing wrong?
P.S. The goal is to obtain a string to save on a file via AJAX.
Your results tell you that the objects in the array that the fullCalendar clientEvents method gives you can't be directly converted to JSON. I get slightly different results on the http://fullcalendar.io page (I get an error about trying to convert a circular structure); I assume that's down to differences either in the FullCalendar version you're using vs. they're using, or differences in how your browser and mine deal with circular structures. Either way, the objects apparently can't be used as-is.
The goal is to obtain a string to save on a file via AJAX.
You can do that by using map on the array to get objects that can be converted to JSON successfully, whitelisting the properties you want (or blacklisting the ones you don't want).
Here's an example whitelisting the start, end, and title properties:
var json = JSON.stringify($("#calendar").fullCalendar("clientEvents").map(function(e) {
return {
start: e.start,
end: e.end,
title: e.title
};
}));
Heres one blacklisting source and any property starting with _:
var json = JSON.stringify($("#calendar").fullCalendar("clientEvents").map(function(e) {
var rv = {};
Object.keys(e)
.filter(function(k) {
return k != "source" && !k.startsWith("_");
})
.forEach(function(k) {
rv[k] = e[k];
});
return rv;
}));
...which worked for me on their site.
Here are ES2015 versions of both of those:
Whitelisting:
let json = JSON.stringify($("#calendar").fullCalendar("clientEvents").map(e => ({
start: e.start,
end: e.end,
title: e.title
})));
Blacklisting:
let json = JSON.stringify($("#calendar").fullCalendar("clientEvents").map(e => {
let rv = {};
Object.keys(e)
.filter(k => k != "source" && !k.startsWith("_"))
.forEach(k => {
rv[k] = e[k];
});
return rv;
}));

Write objects into file with Node.js

I've searched all over stackoverflow / google for this, but can't seem to figure it out.
I'm scraping social media links of a given URL page, and the function returns an object with a list of URLs.
When I try to write this data into a different file, it outputs to the file as [object Object] instead of the expected:
[ 'https://twitter.com/#!/101Cookbooks',
'http://www.facebook.com/101cookbooks']
as it does when I console.log() the results.
This is my sad attempt to read and write a file in Node, trying to read each line(the url) and input through a function call request(line, gotHTML):
fs.readFileSync('./urls.txt').toString().split('\n').forEach(function (line){
console.log(line);
var obj = request(line, gotHTML);
console.log(obj);
fs.writeFileSync('./data.json', obj , 'utf-8');
});
for reference -- the gotHTML function:
function gotHTML(err, resp, html){
var social_ids = [];
if(err){
return console.log(err);
} else if (resp.statusCode === 200){
var parsedHTML = $.load(html);
parsedHTML('a').map(function(i, link){
var href = $(link).attr('href');
for(var i=0; i<socialurls.length; i++){
if(socialurls[i].test(href) && social_ids.indexOf(href) < 0 ) {
social_ids.push(href);
};
};
})
};
return social_ids;
};
Building on what deb2fast said I would also pass in a couple of extra parameters to JSON.stringify() to get it to pretty format:
fs.writeFileSync('./data.json', JSON.stringify(obj, null, 2) , 'utf-8');
The second param is an optional replacer function which you don't need in this case so null works.
The third param is the number of spaces to use for indentation. 2 and 4 seem to be popular choices.
obj is an array in your example.
fs.writeFileSync(filename, data, [options]) requires either String or Buffer in the data parameter. see docs.
Try to write the array in a string format:
// writes 'https://twitter.com/#!/101Cookbooks', 'http://www.facebook.com/101cookbooks'
fs.writeFileSync('./data.json', obj.join(',') , 'utf-8');
Or:
// writes ['https://twitter.com/#!/101Cookbooks', 'http://www.facebook.com/101cookbooks']
var util = require('util');
fs.writeFileSync('./data.json', util.inspect(obj) , 'utf-8');
edit: The reason you see the array in your example is because node's implementation of console.log doesn't just call toString, it calls util.format see console.js source
If you're geting [object object] then use JSON.stringify
fs.writeFile('./data.json', JSON.stringify(obj) , 'utf-8');
It worked for me.
In my experience JSON.stringify is slightly faster than util.inspect.
I had to save the result object of a DB2 query as a json file, The query returned an object of 92k rows, the conversion took very long to complete with util.inspect, so I did the following test by writing the same 1000 record object to a file with both methods.
JSON.Stringify
fs.writeFile('./data.json', JSON.stringify(obj, null, 2));
Time: 3:57 (3 min 57 sec)
Result's format:
[
{
"PROB": "00001",
"BO": "AXZ",
"CNTRY": "649"
},
...
]
util.inspect
var util = require('util');
fs.writeFile('./data.json', util.inspect(obj, false, 2, false));
Time: 4:12 (4 min 12 sec)
Result's format:
[ { PROB: '00001',
BO: 'AXZ',
CNTRY: '649' },
...
]
Could you try doing JSON.stringify(obj);
Like this:
var stringify = JSON.stringify(obj);
fs.writeFileSync('./data.json', stringify, 'utf-8');
Just incase anyone else stumbles across this, I use the fs-extra library in node and write javascript objects to a file like this:
const fse = require('fs-extra');
fse.outputJsonSync('path/to/output/file.json', objectToWriteToFile);
Further to #Jim Schubert's and #deb2fast's answers:
To be able to write out large objects of order which are than ~100 MB, you'll need to use for...of as shown below and match to your requirements.
const fsPromises = require('fs').promises;
const sampleData = {firstName:"John", lastName:"Doe", age:50, eyeColor:"blue"};
const writeToFile = async () => {
for (const dataObject of Object.keys(sampleData)) {
console.log(sampleData[dataObject]);
await fsPromises.appendFile( "out.json" , dataObject +": "+ JSON.stringify(sampleData[dataObject]));
}
}
writeToFile();
Refer https://stackoverflow.com/a/67699911/3152654 for full reference for node.js limits

Check if a object is defined, best practice.

I have the following JSON response from a ajax-request.
var json = {
"response": {
"freeOfChargeProduct": {
"description": "Product",
"orderQty": 5,
"productName": "XYZ",
"qty": 6,
"details": {
"price": 55.5,
"instock": "true",
"focQuantity": 1
}
},
"orderLineId": 4788,
"totalOrderLinePrice": "741.36",
"totalOrderPrice": "1,314.92",
"totalQty": 17
};
The JSON dosen't always return a "freeOfChargeProduct" property. So if I want to get the "freeOfChargeProduct" price, then I have to do the following:
var getFreeOfChargeProductPrice = function() {
var r = json.response;
if (r && r.freeOfChargeProduct && r.freeOfChargeProduct.details) {
return r.freeOfChargeProduct.details.price;
}
return null;
};
No problems. But it's very annoying to check every property in the object, so I created a function that check if a property in a object is defined.
var getValue = function (str, context) {
var scope = context || window,
properties = str.split('.'), i;
for(i = 0; i < properties.length; i++) {
if (!scope[properties[i]]) {
return null;
}
scope = scope[properties[i]];
}
return scope;
};
var price = getValue('json.response.freeOfChargeProduct.details.price');
// Price is null if no such object exists.
Now to my question: Is this a good or bad way to check if a property exists in an object? Any better suggestions/methods?
EDIT:
I don't wan't to use the &&-operator. I am lazy and I'm looking for a reusable method to check if a object (or property of a object) is defined.
:) Thanks!
Use the guard pattern:
if (json.response && json.response.freeOfChargeProduct && json.response.freeOfChargeProduct.details) {
// you can safely access the price
}
This is how the guard pattern works.
if (a && a.b && a.b.c) { ... } else { ... }
The first check is "Does the property a exist?". If not, the else-branch gets executed. If yes, then the next check occurs, which is "Does object a contain the property b?". If no, the else-branch executes. If yes, the final check occurs: "Does the object a.b contain the property c?". If no, the else-branch executes. If yes (and only then), the if-branch executes.
Update: Why is it called "guard pattern"?
var value = a && b;
In this example, the member b (the right operand) is guarded by the && operator. Only if the member a (the left operand) is truthy ("worthy"), only then the member b is returned. If, however, the member a is falsy ("not worthy"), then it itself is returned.
BTW, members are falsy if they return these values: null, undefined, 0, "", false, NaN. Members are truthy in all other cases.
if(x && typeof x.y != 'undefined') {
...
}
// or better
function isDefined(x) {
var undefined;
return x !== undefined;
}
if(x && isDefined(x.y)) {
...
}
This will work for any data type in JavaScript, even a number that is zero. If you are checking for an object or string, just use x && x.y within the if statement, or if you already know that x is an object, if(x.y) ...
You could do something like this:
try{
var focp = json.response.freeOfChargeProduct
var text = "You get " + focp.qty + " of " +
focp.productName +
" for only $" + (focp.qty-focp.details.focQuantity)*focp.details.price +
", You save $" + focp.details.focQuantity*focp.details.price;
$("order_info").innerText = text;
} catch(e) {
// woops, handle error...
}
It would generate a message like this from the provided data in your question if the fields exists:
You get 6 of XYZ for only $277,5, You save $55.5
If the data is non-existing, you'll end up in the catch block. You could always just to a Try, Catch, Forget here if you can't come up with a way to handle the error (Maybe do a new AJAX request for the data?).
This is not a syntax issue as it is a design pattern issue.
Question A.
* Do you have control of the json server?
If the answer to this is no, which I assume, the situation will be all on the client.
Please read this:
http://martinfowler.com/eaaDev/PresentationModel.html
As the server is the source, in this case it will provide the model.
This pattern specifies an additional artifact: The presentation model (PM). In javascript i would suggest two artifacts, a additional for the convertor code.
According to this design pattern the PM is responsible for converting the model to the PM, and back again if necessary. In your case no conversion from PM to M will ever occur.
This means that a js object has a method or constructor that digest the model and translate itself, with the help of the convertor (below).
Doing this you will end up with a PM looking like this:
var OrderlinePM = {
"hasFreeOfCharge": false | true,
"freeOfCharge" : {...}
`enter code here`
this.getFreeOfCharge = function() {
...
}
this.fromModel = function(jsonEntry, convertor) {
//convert this with the convertor ;) to a for this specific view usable OrderlinePM
// also inwith
...
}
enter code here
"orderLineId":0,
"totalOrderLinePrice":"741.36",
"totalOrderPrice":"1,314.92",
"totalQty":17
};
function mySpecialFunctionPMConvertor {
this.fromModel = function() {
... //do strange stuff with the model and poulate a PM with it.
}
}
Ok, I give up trying to format code in this rich text editor :(
You can have several PM:s for diffrent tasks all depending on the same model object.
In addition this will make the converter object testable in something that could be automatically executed.... err ok maby manually, but anyway.
So the problem of the cumbersome reflection code is really not a problem. But cohesion is a issue, expessially in JavaScript.

Categories