I want caching into the “localstorage” the HttpRequest and HttpResponse classes from @angular/common/http
.
The localstorage
only accept string
, therefore i want serialize/unserialize both objects (HttpRequest and HttpResponse) with JSON.stringfy()
and JSON.parse()
.
The problem is HttpRequest
and HttpResponse
are both complex class with some ES6 Map (eg. HttpHeaders) and some getter/setter function, with JSON.stringfy()
and JSON.parse()
the serialization/unserialization don’t return the same object and some information are lost.
There is a way for serialize/unserialize HttpRequest
and HttpResponse
class?
I’m searching for a complete serialization/unserialization (headers, params, body, etc)
In this example there is two method for serialize and unserialize HttpRequest, eg.:
function serializeRequest(angularRequest: HttpRequest): string { return null; // to implement } function unserializeRequest(jsonRequest: string): HttpRequest { return null; // to implement } // this is an example of request const originalRequest = new HttpRequest('POST', 'https://angular.io/docs?foo=bar', {foo: true}, { params: new HttpParams().set('verbose', 'true'), headers: new HttpHeaders({ BAR: 'baz', }), reportProgress: true, responseType: 'json', withCredentials: true }); // serializeRequest trasform HttpRequest in json format const jsonRequest: string = serializeRequest(originalRequest); // unserializeRequest trasform json format to HttpRequest const unserializedRequest : HttpRequest = unserializeRequest(jsonRequest); // unserializedRequest as same object of originalRequest expect(originalRequest).toEqual(unserializedRequest);
the same serialization/unserialization for the response
function serializeResponse(angularResponse: HttpResponse): string { return null; // to implement } function unserializeResponse(jsonResponse: string): HttpResponse { return null; // to implement } // this is an example of response const originalResponse = new HttpResponse({ headers: new HttpHeaders({ BAR: 'baz', }), status: 200, statusText: 'OK', url: 'https://angular.io/docs', body: {foo: true}} ); // serializeResponse trasform HttpResponse in json format const jsonResponse: string = serializeResponse(originalRequest); // unserializeResponse trasform json format to HttpResponse const unserializedResponse: HttpResponse = unserializeResponse(jsonResponse); // unserializedResponse as same object of originalResponse expect(originalResponse).toEqual(unserializedResponse);
Advertisement
Answer
Although I would recommend a Service Worker for Caching, the easiest way that I know is to clone the request/response and then get their information:
function serializeRequest(req: HttpRequest<any>): string { const request = req.clone(); // Make a clone, useful for doing destructive things return JSON.stringify({ headers: Object.fromEntries( // Just a helper to make this into an object, not really required but makes the output nicer request.headers.keys.map( // Get all of the headers (key: string) => [key, request.headers.getAll(key)] // Get all of the corresponding values for the headers ) ), method: request.method, // The Request Method, e.g. GET, POST, DELETE url: request.url, // The URL params: Object.fromEntries( // Just a helper to make this into an object, not really required but makes the output nicer request.headers.keys.map( // Get all of the headers (key: string) => [key, request.headers.getAll(key)] // Get all of the corresponding values for the headers ) ), // The request parameters withCredentials: request.withCredentials, // Whether credentials are being sent respnseType: request.responseType, // The response type body: request.serializeBody() // Serialize the body, all well and good since we are working on a clone }) }
In a similar fashion we can serialize the response as well (assuming T
is JSON compatible, a fair assumption in an HTTP Request):
function serializeResponse(res: HttpResponse<any>): string { const response = res.clone(); return JSON.stringify({ headers: Object.fromEntries( // Just a helper to make this into an object, not really required but makes the output nicer response.headers.keys.map( // Get all of the headers (key: string) => [key, response.headers.getAll(key)] // Get all of the corresponding values for the headers ) ), status: response.status, statusText: response.statusText, url: response.url, body: response // Serialize the body, all well and good since we are working on a clone }) }
And then, since we saved all required information, deserialization is a walk in the park:
function deserializeRequest<T = any>(req: string): HttpRequest<T> { const request = JSON.parse(req); const headers = new HttpHeaders(request.headers); const params = new HttpParams(); // Probably some way to make this a one-liner, but alas, there are no good docs for(let parameter in request.params){ request.params[parameter].forEach((paramValue: string) => params.append(parameter, paramValue)); } return new HttpRequest(request.method, request.url, request.body, { headers, params, respnseType: request.respnseType, withCredentials: request.withCredentials }); } function deserializeResponse<T = any>(res: string): HttpResponse<T> { const response = JSON.parse(res); const headers = new HttpHeaders(response.headers); return new HttpRequest({ headers, body: response.body, status: response.status, statusText: response.statusText, url: response.url, }); }
Playground of the whole thing (although, regrettably the angular types do not load correctly)
Note that I have not tested this in any environment, so this is provided AS-IS, and I am not sure how
expect
would handle twoHttpHeaders
/HttpParams
, especially since they may not have the exact same order.