-
Notifications
You must be signed in to change notification settings - Fork 27.1k
Description
Which @angular/* package(s) are relevant/related to the feature request?
common
Description
There is a common use case to customize JSON parsing in HttpClient, which was already discussed in issue #21079 (closed without solution). There was also a very simple pull request #25027 favoring a JSON_PARSER injection token for customizing the parser. This was rejected in #25027 (comment) as it was considered fairly straightforward to have the user implement a JsonHttpInterceptor as suggested in https://stackblitz.com/edit/angular-ivy-nrdj5q?file=src%2Fapp%2Fcustom-json-parser.ts,src%2Fapp%2Fjson-interceptor.ts.
However, if it were really straightforward, the suggested code would not change the behavior if customized with the original JSON.parse method - but it does in the following way:
- It does not strip away the
XSSI_PREFIXfrom the response body. - It does not return
nullfor an empty response body. - It changes the error handling: In case of a parse error, a
SyntaxErrorfrom theJSON.parsecall is raised on the observable error channel instead of anHttpErrorResponseobject with itserrorproperty set to aHttpJsonParseErrorobject{ error, text }whereerroris theSyntaxErrorandtextis the original response body that could not be parsed.
This means, errors can no longer be handled as described in https://angular.io/guide/http#handling-request-errors. The user may end up in changing the error handling in unrelated HTTP interceptors and for httpClient calls throughout his code.
Proposed solution
Accepting the pull request #25027 would not cause such problems.
Alternatives considered
Modifying the JsonHttpInterceptor from https://stackblitz.com/edit/angular-ivy-nrdj5q?file=src%2Fapp%2Fapp.module.ts to a more adequate solution. The user would need to look up the implementation of HttpXhrBackend (https://github.com/angular/angular/blob/main/packages/common/http/src/xhr.ts#L150) and reimplement the following code in a different way in JsonHttpInterceptor (which is error-prone rather than straightforward):
// ok determines whether the response will be transmitted on the event or
// error channel. Unsuccessful status codes (not 2xx) will always be errors,
// but a successful status code can still result in an error if the user
// asked for JSON data and the body cannot be parsed as such.
let ok = status >= 200 && status < 300;
// Check whether the body needs to be parsed as JSON (in many cases the browser
// will have done that already).
if (req.responseType === 'json' && typeof body === 'string') {
// Save the original body, before attempting XSSI prefix stripping.
const originalBody = body;
body = body.replace(XSSI_PREFIX, '');
try {
// Attempt the parse. If it fails, a parse error should be delivered to the user.
body = body !== '' ? JSON.parse(body) : null;
} catch (error) {
// Since the JSON.parse failed, it's reasonable to assume this might not have been a
// JSON response. Restore the original body (including any XSSI prefix) to deliver
// a better error response.
body = originalBody;
// If this was an error request to begin with, leave it as a string, it probably
// just isn't JSON. Otherwise, deliver the parsing error to the user.
if (ok) {
// Even though the response status was 2xx, this is still an error.
ok = false;
// The parse error contains the text of the body that failed to parse.
body = {error, text: body} as HttpJsonParseError;
}
}
}
if (ok) {
// A successful response is delivered on the event stream.
observer.next(new HttpResponse({
body,
headers,
status,
statusText,
url: url || undefined,
}));
// The full body has been received and delivered, no further events
// are possible. This request is complete.
observer.complete();
} else {
// An unsuccessful request is delivered on the error channel.
observer.error(new HttpErrorResponse({
// The error in this case is the response body (error from the server).
error: body,
headers,
status,
statusText,
url: url || undefined,
}));
}
};