Handle syntax error in JSON coming from API - javascript

Imagine the following syntax error in JSON (, instead of :):
[
{
"name": "anna",
"email": "anna#gmail.com",
"town", "london"
},
...
]
I am wondering if it is possible to handle this error instead of getting an exception, getting the erroneous object, correct the error and go on with the correct version.
Here is a part of my Angular service; I am trying to get text and not JSON data but it does not work...
angular.module('mine', [])
.config(function($sceProvider) {
// Completely disable SCE.
$sceProvider.enabled(false);
})
.config(['$sceDelegateProvider', function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'http://www.mocky.io/v2/5807df4a10000004122b74e2'
]);
}])
.config(function ($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
'request': function(config) {
config.headers.Accept = 'text/plain';
return config;
},
'response': function(response) {
try {
// try to parse it
response.data = JSON.parse(response.data);
} catch (ex) {
// try to fix it
console.log("error " + ex);
console.log(response.data);
response.data = {fixed_data : "data"};
}
// return the corect data.
// note that the original response.data WILL BE CHANGED and this is expected.
return response;
}
};
});
})
angular.module('mine').factory('MyFactory', ['$http','$q', function MyFactory($http,$q) {
return {
getData: function() {
var deferred = $q.defer(),
config = {
params: { }
},
url="http://www.mocky.io/v2/5807df4a10000004122b74e2";
$http.jsonp(url,config)
.then(
function (response) {
deferred.resolve(response.data);
},
function (error) {
console.log(error);
return $q.reject('Error retrieving data');
}
);
return deferred.promise;
}
};
}]);
Is there anyway of directing the above promise into the success callback, retrieving the erroneous JSON and correcting it? How may I code that according to the above example?
Or maybe something easier, how to retrieve text and not JSON data from $http.jsonp as not to be driven to the failure callback?

TL;DR
Edits after further understanding the OP problem:
In the generic case where you want to edit content of a response you can do it with "Interceptors" yet the response should be legitimate to begin with. That is, if you wanted to change numeric strings to integers in an otherwise correct JSON - it would be possible.
In the situation the OP is heaving where the JSON is malformed - it is just not possible!
The long story
First
Getting into a classic XY problem!
You should really ask yourself why is the JSON broken and not attempt to fix it in the client code.
Think of it - You will only get into more problems if you fix it now, and later someone will fix the API - then you will have the broken code.
What if the JSON should have been:
[
{
"name": "anna",
"email": "anna#gmail.com",
"addresses": [{"town": "london", ...}, ...]
},
...
]
Or (god forbid):
[
{
"name": "anna",
"email": ["anna#gmail.com","town", "london"]
},
...
]
You see - my point is - The API is broken, it can be anything. You should fix the API. And if this API is not yours to fix -> use some other API or contact the owner to fix it.
JSONP
JSONP is a way to let APIs to call directly into your code.
You must trust this API. If an API would have giving me malformed JSONs - I would stay away!
In short, the way JSONP works in Angular (or everywhere actually) is by injecting a <script> tag into the DOM with src pointing to the URL of the JSONp request.
The server will pad the JSON data with a function name (most often callback but it can be any globally accessible function (Angular is using angular.callbacks._xyz) and will send it.
The browser then invokes the script that was downloaded from the src.
Now, the thing is that it is the browser calling the script. It is not in the hands of Angular. And that is exactly the problem the OP is confronting - the script must be evaluated as a correct JavaScript to begin with and the browser is doing that, not Angular. It is a MUST. You cannot get in the middle of it. It could pose a security risk if you do. This is why, for instance, the response of a JSONP request will always (by convention...) be returned with MIME type of application/javascript no matter what you ask for.
Warring - here be dragons!
I urge you not to go in this path!
If you are insisting in getting from a JSONP call a JSON with errors (and by errors I mean that the JSON can be parsed as object yet there are some thing you want to change in that object) you could try to add "Interceptors"
.config(function ($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
'request': function(config) {
// here you can edit the request.
return config;
},
'response': function(response) {
// response.data will hold your bad data
// you could edit it
response.data = fix(response.data);
// return the correct data.
return response;
}
};
});
})
Note that you could also Overriding the Default Transformations
Also, make sure to also to:
// Whitelist the JSONP endpoint that we are using to show that we trust it
.config(['$sceDelegateProvider', function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://your.api.url/**'
]);
}])
And if all went well you will be able to call:
//uncomment {jsonpCallbackParam: 'callback'} if your jsonp callback
//parameter at the backend uses some other name but the default 'callback'
$http.jsonp(https://your.api.url/*,{jsonpCallbackParam: 'callback'}*/)
.then(function(response) {
$scope.status = response.status;
$scope.data = response.data;
}, function(response) {
$scope.data = response.data || 'Request failed';
$scope.status = response.status;
});

You can use douglascrockford's JSON-js.If it is not a valid json it will throw an error so that you can catch using try/catch and return a new Promise or simple true/false. If you don't use the library it will fallback to built in parser.
$http({
method: "GET",
url: '../data/data-feed.json'
})
.then(
function (response) {
console.log(response);
try {
JSON.parse(json);
console.log("valid");
} catch (e) {
console.log("invalid");
// correct the invalid json here
}
},
function (error) {
console.log('error');
}
);
Default JSON parser behavior
function parseJSON (jsonString){
try {
var jString = JSON.parse(jsonString);
if (jString && typeof jString === "object") {
return jString;
}
}
catch (e) { }
return false;
};
var inValidJson = '[{"name": "anna","email": "anna#gmail.com","town", "london"}]';
var validJson = '[{"name": "anna","email": "anna#gmail.com","town": "london"}]';
console.log("if invalid returns: ", parseJSON(inValidJson));
console.log("if valid get original object: ",parseJSON(validJson));

Short answer: no there isn't.
Long answer: If your JSON serialisation does not work in the backend, you have basically to parse a string an construct a new Object by yourself. There is no library which does that for you. And think of a different backend service.

I agree to #Florian's answer in general - there is no simple way of doing so.
I think that You should:
try to find a clever way to find the problem place;
replace the comma(s);
parse to JSON anew.
Idea of finding problem place: After every second (even) value there needs to be a comma. After every second (odd one - 1., 3., 5) - a colon. On each New { You have to start a new count. It probably is a pain in the ass, but doable.

It's possible, but cumbersome. For reasons It-Z went into, generally the best practice would be to fix the JSON being served by the API or to find a new API to work with. Assuming you have your reasons for not doing that, here is the logic flow:
catch the error from JSON.parse and feed it to a new function, along with the unparsed response string
in that new function
if you know you're only going to have this one case for your error, create logic to find and fix it; regex will be your friend here
if you're trying to catch multiple types of syntax problems, you'll need more complex regex and much more complex logic
pass the corrected string back to the original JSON.parse function

Related

Service Worker 'Hello World' example

I am learning about Service workers, as I have a usecase to create a fetch listener, that will pass back an arbitrary binary response.
I have no idea where to start. The examples I have seen online talk about making a server request, caching in the service worker, and passing it back. What I really want to do is just pass back my own response, not make a query to the server and cache it!
What I am looking for, as a start is, say something that will once the service worker is active, given the user enters in the browser, (or uses fetch api to get the following url)
http://myapp/helloworld
will show 'Helloworld' in the browser. The service worker will be something like the following. But I have not a clue how make it work.
self.addEventListener('fetch', event => {
// compare end of url with /helloworld
// if match, respond with 'helloword', otherwise fetch the response from the server
});
This is just going to be a very brief, broad overview of how I would tackle the problem.
First, I would follow a guide like this:
https://css-tricks.com/add-a-service-worker-to-your-site/
// Listen for request events
self.addEventListener('fetch', function (event) {
// Get the request
let request = event.request;
...
}
Then you'll use this bit of code as a guideline for what you want to do:
event.respondWith(
fetch(request).then(function (response) {
return response;
}).catch(function (error) {
return caches.match(request).then(function (response) {
return response;
});
})
);
With some modifications.
First, you'll want to check if it's a normal non-/helloworld type of request, and if it is, do something like this:
if (normalRequest) {
event.respondWith(
fetch(request).then(function (response) {
return response;
});
} else {
... TODO
}
And in the TODO section, you'll do your helloworld code - it's not clear to me what you want to do with that, so I can't really go into more detail. But this is the overall structure I would use.

In angular make $http go to catch if server response with {error:'ok'}

$http.get('/data.json')
.then(function(){console.log('ok'})
.catch(function(){console.log('no ok')})
The server response is:
200 OK
content-type: application/json
{error:'cannot get the data'}
I want that the response will go to the .catch not to .then.
I know that I can change the response header from the server, but I want to do it only on the client side.
In other Words:
How I make angular $http promise service, to think that 200 OK status, with "error" key in the response object, will go to catch instead of invoking the then function?
You can use an interceptor:
yourApp.factory('myInterceptor', ['$q', function($q) {
return {
response: function(response) {
if (response.status === 200 && response.data.error) {
return $q.reject(response);
}
else {
return response;
}
}
};
}]);
$httpProvider.interceptors.push('myInterceptor');
$http.get('/data.json')
.then(function(res){
if(res.error === 'cannot get the data'){
return $q.reject(res)
}
return res;
)
.then(function(){console.log('ok'})
.catch(function(){
console.log('no ok')
})
Just as others suggested, you can check for the conditions you want the request to be treated as a failure inside the .then block and reject using angular $q service reject() function
As #yarons pointed out, you could use an interceptor. But, your decision was to always return 200, even in an error case, so why do you want to change this behaviour in your Front End now?
Your logic seems like:
Don't tell the front end to throw an error (maybe to not show in the
dev console or let the user now), but handle it internally as an
error.
For me, if you decide to go for this trick-behaviour, go all the way and don't hack something around. Just look in the then() for the error message.
So go in the then(), as you planned, and then catch your error there with an if clause:
$http.get('/data.json')
.then(function(response){
if(response.data.error) {
$scope.error_message = response.data.error;
return;
}
});

Angular json data showing in console, but not in the view

I am newbie at Angular and I don't really know how to solve this problem. I have looked online and read the documentation, but I haven't found a proper answer. I also asked coworkers about this issue. They couldn't figure it out to assist me, so I thought it would be best to ask you guys about what the best way to solve this is.
Basically, the app is supposed to change the json data when the user clicks link in the menu. It is supposed to grab the current index and then display the corresponding data based on that index in the array. I will post the code that I have so far.
Here is a link to the code on Plunker.
app.factory('quest', ['$http', function($http) {
return $http({
method: 'GET',
url: 'data/study.json'
}).success(function(data) {
return data;
})
.error(function(err) {
return err;
});
}]);
In order to use HTTP requests I would suggest to use the following pattern:
app.factory('quest', function($http, $q) {
var promise;
return {
getQuests: function() {
// $http returns a promise, so we don't need to create one with $q
promise = $http.get('data/study.json')
.then(function(data) {
return data;
}, function(err) {
return $q.reject(err);
});
return promise;
}
}
});
So later you can fetch the factory in your Controller with:
quest.getQuests()
.then(function(data) {
$scope.data = data;
}, function(res) {
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
});
You can find the pattern here: https://stackoverflow.com/a/18383845/1918775
There is also a good example of saving data under a factory so you need only one HTTP request to get data from web-service.
Thanks everyone for your answers. I ended up figuring it out. What I did was created a function to search the array and I passed in the current term. The function returned a data associated with that item.

Angular JSON-RPC: processing error

I am trying to improve an AngularJS service for invoking remote JSON-RPC services. In accordance with the JSON-RPC specification, when an exception occurs at the server side, the response should include an error object with the description of the same:
response = {
jsonrpc: "2.0",
result: null,
error: "Description of the error",
id: 1,
}
... where "id" is the identifier of the request original sent to the server.
The Angular component that I am trying to improve (https://github.com/ajsd/angular-jsonrpc) processes the responses from the server using $http transformers. I modified the original transformer, so that the new one looks like this:
transforms.push(function(data) {
//Original code: return data.id === id ? data.result || data.error : null;
if (data.error !== null) {
throw data.error;
}
if (data.id !== id) {
throw '[jsonrpc] Wrong response ID, id = ' + data.id;
}
return data.result;
});
As you can see, I throw an exception with the description of the error; which is a really poor solution since this service is based on $http promises and, therefore, the invoker of the service will find it difficult to catch the exception.
How can I invoke from within the $http transformer the "error" promise that the user originally sets while invoking the $http service?
$http.get(url).then(success(data) {...}, error(data) {...}};
Is this a correct approach or should I better base my modification on interceptors?
if you left the code as you had it originally, you can define a service which uses the jsonrpc module:
angular.module('myApp').
service('jsonrpcservice', function(jsonrpc) {
var service = jsonrpc.newService('svc');
this.get = service.createMethod('getData');
});
..in your controller somewhere:
jsonrpcservice.get({params}).success(function(result){}).error(function(err){});
you can handle the error in the .error() returned by $http

A design pattern for async requests to handle success, failure, retry ? (javascript)

I'm writing a mobile app with Appcelerator Titanium that makes a lot of different xhr requests. This is not really an Appcelerator Titanium specific question. But if you do write some code, I hope it's javascript.
The app needs to authenticate itself, the user must be logged for some interactions, etc.
I've come to a point where any request might get any kind of response such as:
not authenticated
not logged
bad params
successful
...
The requests are wrapped in different model methods or helpers.
The thing is, I'm not familiar with this kind of app. I was wondering what are the best practices.
Some real questions for example would be:
If the app is not authenticated (token expired, first launch), should the app try to authenticate itself and then send again the request that was denied ? (transparent to user)
Should I send an authentication request each time the app launches and then "forget" about it?
The problem I'm facing is that the code becomes quickly big if I try to handle this for each request. Full of nested callbacks, retry conditions, various events listeners to manage, etc. It just does not feel very "nice". And it's not DRY at all, when what I really need is for any request, check what was wrong, try to fix it (authenticate if not, automatic login if possible or show the login UI, etc..) then if that works retry the original request a couple of times, abort if needed.
I've been looking at the promise pattern but only know theory and don't know if it could be what I need.
So I welcome any advice regarding this particular problem. I wonder how apps like "Facebook" handle this.
Thank you for your help
This question is not easily answered, but let me try to give you some Ideas:
The most important thing, before coding anything in your app, is the API itself. It has to be reliable and adhere to standards. I will not go into too much detail here, but a well written RESTful API can reduce the complexity of your httpClient significantly. It has to respond with standard http status codes and to methods like POST, GET, PUT, DELETE...
A pretty good read is The REST API Design Handbook by George Reese.
My approach to httpClients with Titanium is a single module, which is loaded via require() wherever needed. I stick to one single client at a time, as I had massive problems with multiple parallel calls. Whenever a call is made, the client checks if there is already a call in progress and sends it to a queue if necessary.
Let me show you an example. I have left out lots of stuff for sake of brevity:
// lib/customClient.js
var xhrRequest; // This will be our HTTPClient
var callQueue = []; // This will be our queue
// Register the request
// params are:
// method (e.g. 'GET')
// url (e.g. 'http://test.com/api/v1/user/1')
// done (callback function)
function registerRequest(params) {
if(!xhrRequest) {
sendRequest(params);
} else {
queueRequest(params);
}
}
// This simply sends the request
// to the callQueue
function queueRequest(params) {
callQueue.push(params);
}
// Send the request with the params from register
// Please note that I do not hardcode error messages,
// I just do it here so it is easier to read
function sendRequest(params) {
// Set callback if available and valid
var callback = params.done && typeof(params.done) === "function" ? params.callback : null;
// Set method
var method = params.method || 'GET';
// Create the HTTP Client
xhrRequest = Ti.Network.createHTTPClient({
// Success
onload: function() {
// You can check for status codes in detail here
// For brevity, I will just check if it is valid
if (this.status >= 200 && this.status < 300) {
if(this.responseText) {
// You might want to check if it can be parsed as JSON here
try {
var jsonData = JSON.parse(this.responseText);
if(callback) callback({ success: true, response: jsonData });
} catch(e) {
if(callback) callback({ success: false, errormessage: 'Could not parse JSON data' });
}
processQueue();
} else {
if(callback) callback({ success: false, errormessage: 'No valid response received' });
processQueue();
}
} else {
if(callback) callback({ success: false, errormessage: 'Call response is success but status is ' + this.status });
processQueue();
}
},
// Error
onerror: function(e) {
if(this.responseText) {
try {
var jsonData = JSON.parse(this.responseText);
if(callback) callback({ success: false, reponse: jsonData });
} catch(e) {};
}
processQueue();
},
});
// Prepare and send request
// A lot more can (and should) be configured here, check documentation!
xhrRequest.setTimeout(10000);
xhrRequest.open(method, params.url);
xhrRequest.send();
}
// Checks if there is anything else in the queue
// and sends it
function processQueue() {
xhrRequest = null;
var nextInQueue = callQueue.shift();
if(nextInQueue) sendRequest(nextInQueue);
}
// Our public API
var publicAPI = {
sendRequest: function(params) {
registerRequest(params);
}
};
module.exports = publicAPI;
I can then send a call from any other controller/view
var customClient = require('lib/customClient'); // omit 'lib' if you use alloy
// Send the request
customClient.sendRequest({
method : 'GET',
url : 'http://test.com/api/v1/user/1',
done : function(response) {
Ti.API.debug(JSON.stringify(response));
}
});
Note that this is not complete and does not check for connectivity, has no real error handling etc., but it might help you to get an idea.
I think there is loads of stuff to talk about here, but I will stop here for now...

Categories

Resources