Skip to content
Advertisement

Angular: serialize/unserialize in JSON HttpRequest and HttpResponse object

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 two HttpHeaders/HttpParams, especially since they may not have the exact same order.

Advertisement