diff --git a/goldens/public-api/common/http/http.d.ts b/goldens/public-api/common/http/http.d.ts index 5dfb3aeee715..1cfffc08be85 100644 --- a/goldens/public-api/common/http/http.d.ts +++ b/goldens/public-api/common/http/http.d.ts @@ -1725,7 +1725,7 @@ export declare interface HttpUserEvent { } export declare class HttpXhrBackend implements HttpBackend { - constructor(xhrFactory: XhrFactory); + constructor(xhrFactory: XhrFactory, jsonParser: JsonParser); handle(req: HttpRequest): Observable>; } @@ -1733,6 +1733,12 @@ export declare abstract class HttpXsrfTokenExtractor { abstract getToken(): string | null; } +export declare const JSON_PARSER: InjectionToken; + +export interface JsonParser { + parse(text: string, reviver?: (key: any, value: any) => any): any; +} + export declare class JsonpClientBackend implements HttpBackend { constructor(callbackMap: ɵangular_packages_common_http_http_b, document: any); handle(req: HttpRequest): Observable>; diff --git a/packages/common/http/public_api.ts b/packages/common/http/public_api.ts index 8a434169d7bd..2bcf779a3d97 100644 --- a/packages/common/http/public_api.ts +++ b/packages/common/http/public_api.ts @@ -15,5 +15,5 @@ export {HttpClientJsonpModule, HttpClientModule, HttpClientXsrfModule, HttpInter export {HttpParameterCodec, HttpParams, HttpParamsOptions, HttpUrlEncodingCodec} from './src/params'; export {HttpRequest} from './src/request'; export {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpResponseBase, HttpSentEvent, HttpUploadProgressEvent, HttpUserEvent} from './src/response'; -export {HttpXhrBackend, XhrFactory} from './src/xhr'; +export {HttpXhrBackend, JSON_PARSER, JsonParser, XhrFactory} from './src/xhr'; export {HttpXsrfTokenExtractor} from './src/xsrf'; diff --git a/packages/common/http/src/module.ts b/packages/common/http/src/module.ts index def8022515ec..fde90351cb0c 100644 --- a/packages/common/http/src/module.ts +++ b/packages/common/http/src/module.ts @@ -15,7 +15,7 @@ import {HTTP_INTERCEPTORS, HttpInterceptor, HttpInterceptorHandler, NoopIntercep import {JsonpCallbackContext, JsonpClientBackend, JsonpInterceptor} from './jsonp'; import {HttpRequest} from './request'; import {HttpEvent} from './response'; -import {BrowserXhr, HttpXhrBackend, XhrFactory} from './xhr'; +import {BrowserXhr, HttpXhrBackend, JSON_PARSER, JsonParser, XhrFactory} from './xhr'; import {HttpXsrfCookieExtractor, HttpXsrfInterceptor, HttpXsrfTokenExtractor, XSRF_COOKIE_NAME, XSRF_HEADER_NAME} from './xsrf'; /** @@ -131,6 +131,10 @@ export class HttpClientXsrfModule { } } +export function _JSON(): JsonParser { + return JSON; +} + /** * Configures the [dependency injector](guide/glossary#injector) for `HttpClient` * with supporting services for XSRF. Automatically imported by `HttpClientModule`. @@ -161,6 +165,7 @@ export class HttpClientXsrfModule { {provide: HttpBackend, useExisting: HttpXhrBackend}, BrowserXhr, {provide: XhrFactory, useExisting: BrowserXhr}, + {provide: JSON_PARSER, useFactory: _JSON, deps: []}, ], }) export class HttpClientModule { diff --git a/packages/common/http/src/xhr.ts b/packages/common/http/src/xhr.ts index 2618e8417ab1..ca81d69a6f01 100644 --- a/packages/common/http/src/xhr.ts +++ b/packages/common/http/src/xhr.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; +import {Inject, Injectable, InjectionToken} from '@angular/core'; import {Observable, Observer} from 'rxjs'; import {HttpBackend} from './backend'; @@ -16,6 +16,13 @@ import {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, const XSSI_PREFIX = /^\)\]\}',?\n/; +/** + * Utility for parsing JSON text to JavaScript object + */ +export interface JsonParser { parse(text: string, reviver?: (key: any, value: any) => any): any; } + +export const JSON_PARSER = new InjectionToken('JsonParser'); + /** * Determine an appropriate URL for the response, by checking either * XMLHttpRequest.responseURL or the X-Request-URL header. @@ -70,7 +77,8 @@ interface PartialResponse { */ @Injectable() export class HttpXhrBackend implements HttpBackend { - constructor(private xhrFactory: XhrFactory) {} + constructor(private xhrFactory: XhrFactory, @Inject(JSON_PARSER) private jsonParser: JsonParser) { + } /** * Processes a request and returns a stream of response events. @@ -192,7 +200,7 @@ export class HttpXhrBackend implements HttpBackend { 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; + body = body !== '' ? this.jsonParser.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 diff --git a/packages/common/http/test/xhr_spec.ts b/packages/common/http/test/xhr_spec.ts index ae37b9e5d47d..74d3f0afff91 100644 --- a/packages/common/http/test/xhr_spec.ts +++ b/packages/common/http/test/xhr_spec.ts @@ -8,7 +8,7 @@ import {HttpRequest} from '@angular/common/http/src/request'; import {HttpDownloadProgressEvent, HttpErrorResponse, HttpEvent, HttpEventType, HttpHeaderResponse, HttpResponse, HttpResponseBase, HttpUploadProgressEvent} from '@angular/common/http/src/response'; -import {HttpXhrBackend} from '@angular/common/http/src/xhr'; +import {HttpXhrBackend, JsonParser} from '@angular/common/http/src/xhr'; import {ddescribe, describe, fit, it} from '@angular/core/testing/src/testing_internal'; import {Observable} from 'rxjs'; import {toArray} from 'rxjs/operators'; @@ -33,7 +33,7 @@ const XSSI_PREFIX = ')]}\'\n'; let backend: HttpXhrBackend = null!; beforeEach(() => { factory = new MockXhrFactory(); - backend = new HttpXhrBackend(factory); + backend = new HttpXhrBackend(factory, JSON); }); it('emits status immediately', () => { const events = trackEvents(backend.handle(TEST_POST)); @@ -94,6 +94,19 @@ const XSSI_PREFIX = ')]}\'\n'; const res = events[1] as HttpResponse<{data: string}>; expect(res.body!.data).toBe('some data'); }); + it('supports custom json parser', () => { + const parser: JsonParser = { + parse() { + return 'JSON_RESULT'; + } + }; + backend = new HttpXhrBackend(factory, parser); + const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'}))); + factory.mock.mockFlush(200, 'OK', JSON.stringify({data: 'NOT USED'})); + expect(events.length).toBe(2); + const res = events[1] as HttpResponse; + expect(res.body).toBe('JSON_RESULT'); + }); it('handles a blank json response', () => { const events = trackEvents(backend.handle(TEST_POST.clone({responseType: 'json'}))); factory.mock.mockFlush(200, 'OK', '');