node request module: parsing XML as JSON - json

Until recently I've been fetching XML data using the node request module, and then running that XML through an XML to JSON converter. I discovered by accident that if I set json: true as an option (even knowing the endpoint returns XML, not JSON), I was actually getting back JSON:
var request = require('request');
var options = { gzip: true, json: true, headers: { 'User-Agent': 'stackoverflow question (https://stackoverflow.com/q/52609246/4070848)' } };
options.uri = 'https://api.met.no/weatherapi/locationforecast/1.9/?lat=40.597&lon=-74.26';
request(options, function (error, response, body) {
console.log(`body for ${options.uri}: ${JSON.stringify(body)}`);
});
The above call returns JSON, whereas the raw URL is actually sending XML. Sure enough, with json: false the returned data is XML:
var request = require('request');
var options = { gzip: true, json: true, headers: { 'User-Agent': 'stackoverflow question (https://stackoverflow.com/q/52609246/4070848)' } };
options.uri = 'https://api.met.no/weatherapi/locationforecast/1.9/?lat=40.597&lon=-74.26';
options.json = false; // <<--- the only difference in the request
request(options, function (error, response, body) {
console.log(`body for ${options.uri}: ${body}`);
});
So I thought "that's handy", until I tried the same trick with a different URL that also returns XML, and in this case the returned data is still XML despite using the same request options:
var request = require('request');
var options = { gzip: true, json: true, headers: { 'User-Agent': 'stackoverflow question (https://stackoverflow.com/q/52609246/4070848)' } };
options.uri = 'https://graphical.weather.gov/xml/SOAP_server/ndfdXMLclient.php?whichClient=NDFDgen&lat=40.597&lon=-74.26&product=time-series&temp=tempSubmit=Submit';
request(options, function (error, response, body) {
console.log(`body for ${options.uri}: ${body}`);
});
What is the difference here? How do I get the latter request to return the data in JSON format (so that I can avoid the step of converting XML to JSON myself)? Maybe the endpoint in the first example can detect that JSON is requested and it does in fact return JSON rather than XML?
EDIT weirdly, the first request is now returning XML rather than JSON even with json: true. So maybe this behaviour was down to what was being sent from the endpoint, and they've changed this even since I posted a few hours ago

So now that the behavior is unrepeatable, the answer is less useful for your particular problem, but I think it's worth pointing out that when you set json:true on the request module, it does a few things under the hood for you:
Sets the Accept header to 'application/json'
Parses the response body using JSON.parse()
Request types with a body also get the body automatically serialized as JSON
Request types with a body also get the Content-Type header added as 'application/json'
So perhaps they did change it, but there are plenty of web services I've seen that will detect the content-type to send based on the Accept header and respond appropriately for some set of types that make sense (usually XML or JSON, but sometimes CSV, TXT, HTML, etc).

To handle XML query, I usually do something like this using the request module:
import parser from "xml2json";
const resp = await rp({
method: "POST",
url: 'some url',
form: {xml_query}, // set XML query to xml_query field
});
const parsedData = parser.toJson(resp, {
object: true, // returns a Javascript object instead of a JSON string
coerce: true, // makes type coercion.
});

Related

Handling (read) of Base64 encoded files in a Logic App, and post to endpoint

I have a Logic App that gets the contents from a SharePoint (.xlsx) and posts the body to an endpoint to get processed. now the content I see is a base64-encoded file, what I wanted to do was to post this data as is.
when I try to post it using postman it gets accepted successfully but when it is posted form the Logic app I get
BadRequest. Http request failed: the content was not a valid JSON.
but I can see that the body that was meant to be sent is of the type, which is a valid Json
{
"$content-type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"$content": "AA....VeryLong...B1241BACDFA=="
}
also tried this expression
decodeBase64(triggerBody()?[body('getFile')])
but I get a different error
InvalidTemplate. Unable to process template language expressions in action 'HTTP' inputs at line '1' and column '2565': 'The template language expression 'decodeBase64(triggerBody()?[body('getFile')])' cannot be evaluated because property '{
"$content-type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"$content": "UEsDBBQABgAIAAAAIQDuooLHjAEAAJkGAAATAAgCW0Nvb...
What I want to achieve is simple really I want to post to my end point the Json as is or the contents of the base64Encoded string.
If you decode the content with base64 you will find the content is garbled. This is because the content is in ooxml format then encoded with base64. And in logic app you could not decode the ooxml.
First solution, you could use Azure Function to write a method to read the document then return the content. Then in logic app call the function to get the content.
Second solution, change your file to a directly readable file(like .txt file), this way I tried and you could parse it Json.
Can you send me your postman request save it in collect and then export as file.
I think what you can do in postman can be done in logic app too.
Or send me your code I can modify it.Make sure you have post as body and it matches the parameter of Post at web api level.
var Webrequestdata = {
"webserviceurl": "http://examplecom/u/b/b/e.ee.msg",
"username": "123"
};
$.ajax({
cache: false,
type: "POST",
url: 'http://example.com/res/api/Outlookapi',
data: JSON.stringify(Webrequestdata),
contentType: "application/json",
success: function (result) {
console.log("email sent successfully");
},
error: function (response) { alert('Error: ' + response.responseText); }
});
after looking at your statement:
Logic App gets the contents from a SharePoint (.xlsx) and posts the
body to an endpoint to get processed. now the content I see is a
base64-encoded file
i assume your endpoint is still looking for a content type -
vnd.openxmlformats-officedocument.spreadsheetml.sheet
but you are passing a Base64 String
1) check if your "content-type" header is correctly placed change it to application/json if possible
2) Make sure you are processing the base64 string correctly.
In my case, I extracted the base64 string from an image file using Javascript. I got special keys at the start of base64 string like ''data:image/png;base64','ACTUAL BASE 64 STRING...' so i removed the special keys before the actual base64 string with some regular expression.
This sample Json request might help in attaching the base64 Content
Make sure you are converting your request to JSON using JSON.stringify
//var finalString = srcData.replace('data:image/png;base64,','');
var finalString = srcData.replace(/^,+?(\,)/, '');
finalString = srcData.substring(srcData.indexOf(',')+1, srcData.length);
var data = JSON.stringify({
"requests": [
{
"image": {
"content": finalString
},
"features":
[
{
"type": "DOCUMENT_TEXT_DETECTION",
"maxResults": 1
}
]
}
]
});
This is the XMLHttpRequest i used:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
var obj = JSON.parse(this.responseText);
try
{
//do something
}catch(err)
{
//do something
}
}
});
try {
xhr.open("POST", "https://vision.googleapis.com/v1/images:annotate?key=blablabla");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("cache-control", "no-cache");
xhr.setRequestHeader("Postman-Token", "111111111");
xhr.send(data);
}
catch(err) {
//do something
}

Sending larger JSON strings via HTTP using Node

I am working on sending JSON data from my HTTP server to my client. I have been successful in sending smaller sized JSON responses to the client, but once I have to wait and collect all the data before sending, my code does not function properly. I am using the .on('data', function (){}) method to collect the data and build it back up and the .on('end', function(){}) to try sending the data, but my code never enters either of these methods.
My code for sending the larger sized JSON data is below:
exports.sendJson = function (req, resp, data) {
resp.writeHead(200, "Valid EndPoints", { "Content-Type": "application/json" });
var payload = '';
resp.on('data', function(data){
payload += data;
})
.on('end', function(){
resp.write(JSON.stringify(payload));
});
resp.end();
};
My code that works fine for smaller sized JSON data is as follows:
exports.sendJson = function (req, resp, data) {
resp.writeHead(200, { "Content-Type": "application/json" });
if(data) {
resp.write(JSON.stringify(data));
}
resp.end();
};
Thanks in advance. I'm actually pretty happy I got this far.
A HTTP server response is a Writable; it does not emit data events.
If the thing you pass in to sendJson's data parameter is an object, you just do exactly what you're doing in the second example. There is no need to buffer anything. (If the thing you pass to sendJson is a stream, just pipe it to the response.)
(When parsing received JSON, you must buffer the data you get from a Readable -- the HTTP response on the client.)

How to return a JSON object from an Azure Function with Node.js

With Azure Functions, what do you need to do to return a JSON object in the body from a function written in node.js? I can easily return a string, but when I try to return a json object as shown below I appear to have nothing returned.
context.res = {
body: jsonData,
contentType: 'application/json'
};
Based on my recent testing (March 2017). You have to explicitly add content type to response headers to get json back otherwise data shows-up as XML in browser.
"Content-Type":"application/json"
res = {
status: 200, /* Defaults to 200 */
body: {message: "Hello " + (req.query.name || req.body.name)},
headers: {
'Content-Type': 'application/json'
}
};
Full Sample below:
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
context.log(context);
if (req.query.name || (req.body && req.body.name)) {
res = {
// status: 200, /* Defaults to 200 */
body: {message: "Hello " + (req.query.name || req.body.name)},
headers: {
'Content-Type': 'application/json'
}
};
}
else {
res = {
status: 400,
body: "Please pass a name on the query string or in the request body"
};
}
context.done(null, res);
};
If your data is a JS object, then this should just work, e.g.
module.exports = function(context, req) {
context.res = {
body: { name: "Azure Functions" }
};
context.done();
};
This will return an application/json response.
If instead you have your data in a json string, you can have:
module.exports = function(context, req) {
context.res = {
body: '{ "name": "Azure Functions" }'
};
context.done();
};
Which will return an application/json response because it sniffs that it is valid json.
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
if (req.query.name || (req.body && req.body.name)) {
context.res = {
// status: 200, /* Defaults to 200 */
body: {"data":"Hello"},
headers: {
'Content-Type': 'application/json'
}
};
}
else {
// res = {
// status: 400,
// body: "Please pass a name on the query string or in the request body"
// };
}
context.done(null,res);
I would like to add one more point. Apart from making the body: a JSON object, the request should also contain proper headers telling server what content type we are interested in. I could see that same Azure function when just invoked via browser using URL gives XML response, but when invoking from script or tools like Postman it gives JSON.
I feel like the answer has been given but it hasn't been clearly presented so I thought I'd answer as well in case it will help anyone coming behind me. I too have created a function that most definitely returns a Javascript object but if I copy and paste the URL in the Azure Function UI and just open a new tab in Chrome and try to view the output, I actually get back an XML document that tells me there's an error (not surprising there's an error as many characters in the Javascript would have blown up the XML). So, as others have mentioned, the key is sending the appropriate headers with your request. When you copy/paste the URL into your browser, the browser is sending a request header that looks similar to this:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
When that happens, you see the XML return as described in this link:
https://github.com/strongloop/strong-remoting/issues/118
In order to get around this problem and see what the data would look like with a JSON request, either use a utility like Postman:
https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en
Accept: application/json
Or use a CURL command and pass in the proper Accept header.
As you can see in the screenshot above, when I provided the proper header, I get back the JSON response I would expect.
You can also use JSON.stringify() to make a valid json string out of your js-object:
jsonData = { value: "test" }:
context.res = {
body: JSON.stringify(jsonData)
};

HTTP Request returning empty element

I try to get a JSON object from a webservice with
MashupPlatform.http.makeRequest(url, {
method: 'GET',
requestHeaders: {"Accept": "application/json"},
forceProxy: true,
onSuccess: function (response) {
console.log("response: " + JSON.stringify(response));
success(response);
},
onFailure: function (response) {
error(response);
},
onComplete: function () {
complete();
}
});
but in the console every time an empty element ({}) gets logged. If I use curl to request that exact same URL I get the response I need. Is the wirecloud proxy unable to request application/json? In my browsers network analysis I see the request including the correct response, but the success function seems to not get that data.
WireCloud proxy supports application/json without any problem. Although the problem may be caused by other parameters, I think that your problem is related to a bad access to the response data. You should use response.responseText instead of using directly the response object (see this link for more info).

JSON deserialization with angular.fromJson()

Despite from the AngularJS documentation for angular.fromJson being spectacular, I still don't know how to use it to its fullest potential. Originally I have just been directly assigning the JSON data response from an HTTP request to a $scope variable. I've recently noticed that Angular has a built-in fromJson() function, which seems like something I'd want to use. If I use it, is it safer and can I access data elements easier?
This is how I've been doing it:
$http({
method: 'GET',
url: 'https://www.reddit.com/r/2007scape.json'
}).then(function success(response) {
var mainPost = response; // assigning JSON data here
return mainPost;
}, function error(response) {
console.log("No response");
});
This is how I could be doing it:
$http({
method: 'GET',
url: 'https://www.reddit.com/r/2007scape.json'
}).then(function success(response) {
var json = angular.fromJson(response); // assigning JSON data here
return json;
}, function error(response) {
console.log("No response");
});
It is pointless to convert the response to json as angular does it for you. From angular documentation of $http:
Angular provides the following default transformations:
Request transformations ($httpProvider.defaults.transformRequest and $http.defaults.transformRequest):
If the data property of the request configuration object contains an object, serialize it into JSON format.
Response transformations ($httpProvider.defaults.transformResponse and $http.defaults.transformResponse):
If XSRF prefix is detected, strip it (see Security Considerations section below).
If JSON response is detected, deserialize it using a JSON parser.