Protobuf : WebApi -> JS - Decoded object is empty - javascript

I would like to send an object from a WebApi controller to an Html page through an Ajax Request.
When I receive the object in JS, it's empty. But server-side the object isn't empty because when I look at the byte[].length it's greater than 0.
Server-side, I use the dll provided by Google.
JS side, I use the ProtobufJS library. This is my .proto file :
syntax="proto3";
message Container {
repeated TestModel2 Models = 1;
}
message TestModel2 {
string Property1 = 1;
bool Property2 = 2;
double Property3 = 3;
}
Server code :
var container = new Container();
var model = new TestModel2
{
Property1 = "Test",
Property2 = true,
Property3 = 3.14
};
container.Models.Add(model);
Base64 data :
ChEKBFRlc3QQARkfhetRuB4JQA==
JS decoding :
var ProtoBuf = dcodeIO.ProtoBuf;
var xhr = ProtoBuf.Util.XHR();
xhr.open(
/* method */ "GET",
/* file */ "/XXXX/Protobuf/GetProtoData",
/* async */ true
);
xhr.responseType = "arraybuffer";
xhr.onload = function (evt) {
var testModelBuilder = ProtoBuf.loadProtoFile(
"URL_TO_PROTO_FILE",
"Container.proto").build("Container");
var msg = testModelBuilder.decode64(xhr.response);
console.log(JSON.stringify(msg, null, 4)); // Correctly decoded
}
xhr.send(null);
Result object in JS console :
{
"Models": []
}
bytebuffer.js
protobuf.js v5.0.1

Finally i solved the problem by myself.
It was the client-side which was in fault.
In fact the xhr.response is JSON format so it was between double quotes "ChEKBFRlc3QQARkfhetRuB4JQA==". I had to JSON.parse my response.enter code here
I removed the xhr.responseType = "arraybuffer";
Here is my code now :
var ProtoBuf = dcodeIO.ProtoBuf;
var xhr = ProtoBuf.Util.XHR();
xhr.open(
/* method */ "GET",
/* file */ "/XXXX/Protobuf/GetProtoData",
/* async */ true
);
// xhr.responseType = "arraybuffer"; <--- Removed
xhr.onload = function (evt) {
var testModelBuilder = ProtoBuf.loadProtoFile(
"URL_TO_PROTO_FILE",
"Container.proto").build("Container");
var msg = testModelBuilder.decode64(JSON.parse(xhr.response)); <-- Parse the response in JSON format
console.log(msg); // Correctly decoded
}
xhr.send(null);

Related

adjust onload function to be used with async/await [duplicate]

This question already has answers here:
How do I promisify native XHR?
(6 answers)
Closed 3 months ago.
I'm working on converting an xlsx I get from a URL to a JSON object in the Browser.
This answer works --> https://stackoverflow.com/a/52237535/5079799
But, I can't get the code to wait for the reponse. All the answers online seem to be about images and/or using an input filereader, but I'm fetching a URL.
How I can wrap this all in a function that says:
Get XLSX from URL
Convert to JSON
Return JSON
Here is what I've been messing with so far, but it always ends with the outside variables unset but the inside works correctly.
async function Outside_Test(){
var reso = await Get_JSON()
console.log('reso_out')
console.log(reso)
}
async function Get_JSON() {
var url = "http://myspreadsheet.xlsx"
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
//oReq.onload =
return oReq.send()
.then(function (oReq) {
var arraybuffer = oReq.response;
/* convert data to binary string */
var data = new Uint8Array(arraybuffer);
var arr = new Array();
for (var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
var bstr = arr.join("");
/* Call XLSX */
var workbook = XLSX.read(bstr, {
type: "binary"
});
/* DO SOMETHING WITH workbook HERE */
var first_sheet_name = workbook.SheetNames[0];
/* Get worksheet */
var worksheet = workbook.Sheets[first_sheet_name];
var reso = (XLSX.utils.sheet_to_json(worksheet, {
raw: true
}));
console.log('inside-reso')
return reso
})
}
You'll want to return a Promise from Get_JSON that resolves when .onload is called
something like
function Get_JSON() {
return new Promise((resolve, reject) => {
var url = "http://myspreadsheet.xlsx"
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = function () {
/* convert data to binary string */
/* Call XLSX */
/* DO SOMETHING WITH workbook HERE */
/* Get worksheet */
console.log('inside-reso')
resolve(reso);
});
oReq.onerror = reject;
oReq.send() ;
});
}
Note: no need for Get_JSON to be async ... since you never need to await
Another alternative I guess is
async function Get_JSON() {
const arrayBuffer = await new Promise((resolve, reject) => {
var url = "http://myspreadsheet.xlsx"
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = () => resolve(oReq.response);
oReq.onerror = reject;
oReq.send();
});
/* convert data to binary string */
var data = new Uint8Array(arraybuffer);
// ...
/* Call XLSX */
// ...
/* DO SOMETHING WITH workbook HERE */
// ...
/* Get worksheet */
// ...
return reso;
}
I kinda like this way, using async/await makes it clear this will return a Promise

How to stop number being converted to string in xmlHttpRequest?

How do I stop a number being converted to a string when adding an element to a JSON file on a server using xmlHttpRequest?
The following code updates my .json file with the element but the number (var importance) is a string by the time it arrives at the server... and I can't work out why.
This is where I format my input data and create the xmlHttpRequest.. (script.js):
btnSubmit.onclick = submitTask;
function submitTask() {
inputTask = document.querySelector('#task');
inputImportance = document.querySelector('#importance');
var task = inputTask.value;
var importance = Number(inputImportance.value);
console.log("User Input: ",task, importance);
//Setup XML HTTP Request
var xhr = new XMLHttpRequest();
xhr.open('POST', api_url_add +'/'+ task +'/'+ importance, true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
//Receive response from server.
xhr.onload = function() {
response = JSON.parse(xhr.response);
console.log(response);
}
xhr.send();
}
And this is the server side code (server.js):
// ADD task to the list (task, importance)
app.post('/add/:task/:importance?', addTask);
function addTask(request, response) {
var data = request.params;
console.log('Submitted to server:','\n', data);
var task = data.task;
var importance = Number(data.importance);
var reply;
if (!importance) {
var reply = {
msg: "Importance value is required."
}
} else {
var element = data;
tasks['taskList'].push(element);
fs.writeFile('tasks.json', JSON.stringify(tasks, null, 2), function(err){
console.log('all done')
})
response.send(reply);
}
}
Thanks for all of your help.

Empty response when pushing arrays to JSON

I can't figure out why my multidimensional array in JSON always is empty in the response. If I declare my JSON static like this..
var data = {
foo: 123,
bar: 456,
cars: [
{ name:"Ford", test: 4},
{ name:"BMW" },
{ name:"Fiat"}
]
};
Response:
(index):78 Success
(index):79 {"foo":123,"bar":456,"cars":[{"name":"Ford","test":4},{"name":"BMW"},{"name":"Fiat"}]}
So this works, but when I add arrays dynamically the response is empty..
var data = {
foo: 123,
bar: 456,
};
data.cars: [];
function getMousePos(e) {
return {x:e.clientX,y:e.clientY};
}
document.onmousemove=function(e) {
var mousePos = getMousePos(e);
data.cars.push({x: mousePos.x, y: mousePos.y});
console.log(JSON.stringify(data));
};
var createCORSRequest = function(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// Most browsers.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// IE8 & IE9
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// CORS not supported.
xhr = null;
}
return xhr;
};
var url = 'http://localhost:80/t';
var method = 'POST';
var xhr = createCORSRequest(method, url);
xhr.onload = function() {
console.log("Success");
console.log(xhr.responseText);
};
xhr.onerror = function() {
console.log("Error");
};
xhr.send(JSON.stringify(data));
The console before I send..
{"foo":123,"bar":456,"cars":[{"x":320,"y":8},{"x":321,"y":20}]}
The response I get..
(index):79 Success
(index):80 {"foo":123,"bar":456,"cars":[]}
The "cars" array always ends up empty in the response when I push arrays to the JSON string. I have read every stackoverflow thread I can find about this but can't figure out the problem.
Response code on server
public function getJson(Request $request) {
$content = $request->json()->all();
return $content;
}
I should also point out that i'm using Laravel 5.4 on the response server.
I could see 2 mistakes:
Define Cars object like data.cars = []; rather using data.cars: [];
Ajax calls are asynchronous in nature, based on the code which you have written xhr.send will be called before document.onmousemove function.
onmousemove requires mousemove event to trigger but xhr.send is not inside any function and hence getting called as soon as page is getting loaded.
So you will have to make 2 changes:
Define Cars object like data.cars = [];
Call xhr.send method after assignment of data in mousemove function i.e. inside mousemove or other function

Why does AJAX output comes with wrong encoding?

I'm getting a file from a server with AJAX (Angular).The file is a simple XLSX document, sent like this:
ob_start();
$file = \PHPExcel_IOFactory::createWriter($xls, 'Excel2007');
$file->save('php://output');
$response->setContent(ob_get_clean());
$response->headers->replace(array(
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition' => 'attachment;filename=file.xlsx"'
));
When I make a request from frontend, I use Accept header too. Then I save the file with angular-file-saver using FileSaver.js and Blob.js.
But the received file is corrupt and I can't open it in Excel: it's size is (for example) 12446 bytes, but Chrome's DevTools Network tab shows responses Content-Length header as 7141 bytes.
How can I solve this problem?
UPD:
I'm sending a request like this:
$http.get(baseURL + '/' + entity + '/export/?' + condition + sort, {
headers: {'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8'}
});
and downloading file just like this:
var data = new Blob([response.data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'});
FileSaver.saveAs(data, 'file.xlsx');
The way I got around the problem was using plain JS AJAX, instead of AngularJS. (There might be a problem with AngularJS and JQuery handling binary responses.)
This should work:
var request = new XMLHttpRequest();
request.open('GET', 'http://yourserver/yourpath', true);
request.responseType = 'blob';
request.onload = function (e) {
if (this.status === 200) {
var blob = this.response;
if (window.navigator.msSaveOrOpenBlob) {
var fileNamePattern = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
window.navigator.msSaveBlob(blob, fileNamePattern.exec(request.getResponseHeader("content-disposition"))[1]);
} else {
var downloadLink = window.document.createElement('a');
var contentTypeHeader = request.getResponseHeader("Content-Type");
var b = new Blob([blob], { type: contentTypeHeader });
downloadLink.href = window.URL.createObjectURL(b);
var fileNamePattern = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
downloadLink.download = fileNamePattern.exec(request.getResponseHeader("content-disposition"))[1];
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(b);
}
}
};
request.send();
Code is based on this and this.
FYI, I found that new Blob([response.data], ...) returns almost double the size of response.data when response.data is not returned as blob, but text/plain or application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. To get around it, you need to pass it an array of bytes instead:
var i, l, d, array;
d = this.result;
l = d.length;
array = new Uint8Array(l);
for (var i = 0; i < l; i++){
array[i] = d.charCodeAt(i);
}
var b = new Blob([array], {type: 'application/octet-stream'});
window.location.href = URL.createObjectURL(b);
Code is from here.
Anyways, since the AJAX response is not correct using AngularJS, you won't get a valid xlsx file this way. You need to go with vanilla JS.

JSON Parse File Path

I'm stuck trying to get the correct path to the local file. I have the following directories:
Resources ->
data ->
file.json
js ->
folder ->
script.js
html ->
folder ->
file1.html
I'm executing script.js from file1.html, with js code:
var answers = JSON.parse('../../data/file.json');
alert(answers);
But it doesn't work, even alert is not starting.
What is wrong?
Also I've tried this:
function readJSON(file) {
var request = new XMLHttpRequest();
request.open('GET', file, false);
request.send(null);
if (request.status == 200)
return request.responseText;
};
var temp = readJSON('../../data/file.json');
alert(temp);
Alert undefined in this case.
Since it is in the directory data/, You need to do:
file path is '../../data/file.json'
$.getJSON('../../data/file.json', function(data) {
alert(data);
});
Pure JS:
var request = new XMLHttpRequest();
request.open("GET", "../../data/file.json", false);
request.send(null)
var my_JSON_object = JSON.parse(request.responseText);
alert (my_JSON_object.result[0]);
This solution uses an Asynchronous call. It will likely work better than a synchronous solution.
var request = new XMLHttpRequest();
request.open("GET", "../../data/file.json", false);
request.send(null);
request.onreadystatechange = function() {
if ( request.readyState === 4 && request.status === 200 ) {
var my_JSON_object = JSON.parse(request.responseText);
console.log(my_JSON_object);
}
}
Loading local JSON file
Use something like this
$.getJSON("../../data/file.json", function(json) {
console.log(json); // this will show the info in firebug console
alert(json);
});
var request = new XMLHttpRequest();
request.open("GET","<path_to_file>", false);
request.send(null);
var jsonData = JSON.parse(request.responseText);
This code worked for me.
If Resources is the root path, best way to access file.json would be via /data/file.json
My case of working code is:
var request = new XMLHttpRequest();
request.open("GET", "<path_to_file>", false);
request.overrideMimeType("application/json");
request.send(null);
var jsonData = JSON.parse(request.responseText);
console.log(jsonData);
Even if long time answerd, I here is another that does not need to create a request. I created a lg.js script containing a class and some functions (setLg, de, en, ...)
Each "lg" function (de, en, fr, ...) provides a json object containing the translations.
"use strict"
class Lg {
constructor() { this.tr = this.en(); }
setLg(l) {
if l == "de" this.tr = this.de();
if l == "en" this.tr = this.en();
de() {
"item": "Artikel",
"login": "Login"
}
en() {
"item": "Item",
"login": "login"
}
}
var lg = new Lg(); //global accessable object
You can then use it by "lg.tr.item"
You could also create a small function, that checks if the "key" is in the JSON object to have some kind of error checking.

Categories

Resources