使用Angular 4.3 HttpClient解析日期

时间:2017-10-04 07:26:06

标签: json angular date angular-httpclient

我目前正在切换到Angular 4.3的新HttpClient。一个优点是我可以在GET方法上指定类型信息,并将返回的JSON解析为给定类型,例如。

this.http.get<Person> (url).subscribe(...)

但不幸的是,JSON中的所有日期都被解析为结果对象中的数字(可能是因为Java Date对象在后端被序列化为数字)。

使用旧的Http我在调用JSON.parse()时使用了reviver函数:

this.http.get(url)
  .map(response => JSON.parse(response.text(), this.reviver))

在reviver函数中,我从数字中创建了日期对象:

reviver (key, value): any {
  if (value !== null && (key === 'created' || key === 'modified'))
    return new Date(value);

  return value;
}

新的HttpClient是否有类似的机制?或者在解析JSON时进行转换的最佳做法是什么?

5 个答案:

答案 0 :(得分:15)

不幸的是,似乎没有办法将reviver传递给Angular HttpClient中使用的JSON.parse方法。以下是他们调用JSON.parse的源代码: https://github.com/angular/angular/blob/20e1cc049fa632e88dfeb00476455a41b7f42338/packages/common/http/src/xhr.ts#L189

如果响应类型设置为“json”,Angular将仅解析响应(您可以在第183行看到此信息)。如果您没有指定响应类型,则Angular默认为“json”(https://github.com/angular/angular/blob/20e1cc049fa632e88dfeb00476455a41b7f42338/packages/common/http/src/request.ts#L112)。

所以你可以使用响应类型的“text”并自己解析json。或者你可能只是懒惰和序列化,然后反序列化响应(JSON.parse(JSON.stringify(res.body), reviver))。

您可以创建一个拦截器,而不是修改每个调用,如下所示:

<强> JSON-interceptor.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';

// https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts#L18
const XSSI_PREFIX = /^\)\]\}',?\n/;

/**
 * Provide custom json parsing capabilities for api requests.
 * @export
 * @class JsonInterceptor
 */
@Injectable()
export class JsonInterceptor implements HttpInterceptor {

  /**
   * Custom http request interceptor
   * @public
   * @param {HttpRequest<any>} req
   * @param {HttpHandler} next
   * @returns {Observable<HttpEvent<any>>}
   * @memberof JsonInterceptor
   */
  public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.responseType !== 'json') {
      return next.handle(req);
    }
    // convert to responseType of text to skip angular parsing
    req = req.clone({
      responseType: 'text'
    });

    return next.handle(req).map(event => {
      // Pass through everything except for the final response.
      if (!(event instanceof HttpResponse)) {
        return event;
      }
      return this.processJsonResponse(event);
    });
  }

  /**
   * Parse the json body using custom revivers.
   * @private
   * @param {HttpResponse<string>} res
   * @returns{HttpResponse<any>}
   * @memberof JsonInterceptor
   */
  private processJsonResponse(res: HttpResponse<string>): HttpResponse<any> {
      let body = res.body;
      if (typeof body === 'string') {
        const originalBody = body;
        body = body.replace(XSSI_PREFIX, '');
        try {
          body = body !== '' ? JSON.parse(body, (key: any, value: any) => this.reviveUtcDate(key, value)) : null;
        } catch (error) {
          // match https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts#L221
          throw new HttpErrorResponse({
            error: { error, text: originalBody },
            headers: res.headers,
            status: res.status,
            statusText: res.statusText,
            url: res.url || undefined,
          });
        }
      }
      return res.clone({ body });
  }

  /**
   * Detect a date string and convert it to a date object.
   * @private
   * @param {*} key json property key.
   * @param {*} value json property value.
   * @returns {*} original value or the parsed date.
   * @memberof JsonInterceptor
   */
  private reviveUtcDate(key: any, value: any): any {
      if (typeof value !== 'string') {
          return value;
      }
      if (value === '0001-01-01T00:00:00') {
          return null;
      }
      const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
      if (!match) {
          return value;
      }
      return new Date(value);
  }
}

然后你必须在你的模块中提供它:

<强> *。module.ts

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { JsonInterceptor } from '...';

@NgModule({
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: JsonInterceptor,
            multi: true
        }
    ]
})
...

在这个例子中,我试图模仿角度尽可能多地进行解析。它们似乎没有导出HttpJsonParseError所以我无法将错误转换为该类型。它可能并不完美,但我希望它可以得到这个想法。

这是一个正在运行的示例(在控制台中查看已解析的日期):https://stackblitz.com/edit/json-interceptor

我在这里创建了一个功能请求: https://github.com/angular/angular/issues/21079

答案 1 :(得分:13)

这对我有用:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class ApiInterceptor implements HttpInterceptor {
  private dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)$/;

  private utcDateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/;

  constructor() { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .do((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          this.convertDates(event.body);
        }
      });
  }

  private convertDates(object: Object) {
    if (!object || !(object instanceof Object)) {
      return;
    }

    if (object instanceof Array) {
      for (const item of object) {
        this.convertDates(item);
      }
    }

    for (const key of Object.keys(object)) {
      const value = object[key];

      if (value instanceof Array) {
        for (const item of value) {
          this.convertDates(item);
        }
      }

      if (value instanceof Object) {
        this.convertDates(value);
      }

      if (typeof value === 'string' && this.dateRegex.test(value)) {
        object[key] = new Date(value);
      }
    }
  }
}

这个优于bygrace答案的优点是你不需要自己解析json,你只需要在解析完角度后转换日期。

这也适用于数组和嵌套对象。 我修改了this它应该支持数组。

答案 2 :(得分:5)

你仍然可以,但你需要从rxjs导入map() - 运算符,如下所示:

import 'rxjs/add/operator/map';

然后就像迭戈指出的那样,你可以像这样使用map

return this.http.get<BlogPost>(url)
.map(x => {
x.published = new Date(String(x.published));
    return x;
})
[...]

答案 3 :(得分:2)

您可以使用:

this.http.get(url, { responseType: 'text' })
    .map(r => JSON.parse(r, this.reviver))
    .subscribe(...)

答案 4 :(得分:2)

Jonas Stensved's answer类似,但使用管道:

import { map } from "rxjs/operators";

this.http.get(url)
  .pipe(
    map(response => {
      response.mydate = new Date(response.mydate);
      return response;
    })

请注意map运算符的不同导入语法。

管道在RxJS 5.5中引入。它们有助于导入处理,代码可读性和减少包大小。请参阅Understanding Operator Imports