【问题标题】:Parse date with Angular 4.3 HttpClient使用 Angular 4.3 HttpClient 解析日期
【发布时间】:2020-05-25 21:01:17
【问题描述】:

我目前正在切换到 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 时进行转换的最佳做法是什么?

【问题讨论】:

  • 难道不能将此 reviver 函数重新定位到您期望的对象,即 Person 吗?
  • Person 很可能是一个接口,所以它没有方法

标签: json angular date angular-httpclient


【解决方案1】:

这对我有用:

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,只需在 angular 解析完成后转换日期即可。

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

【讨论】:

  • 我添加了一个不带“T”的新模式,它对我有用! :)
  • 我在使用此解决方案时遇到了性能问题。当响应大小约为 2MB 时,大约需要 20 秒才能完成。虽然当我使用 JSON.parse(JSON.stringify(res.body), reviver) 时只花了大约 3 秒。
【解决方案2】:

不幸的是,似乎没有办法将 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)。

因此,您可以使用“文本”响应类型并自己解析 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
        }
    ]
})
...

在示例中,我尝试尽可能多地模仿 angular 是如何进行解析的。他们似乎没有导出 HttpJsonParseError 所以我无法将错误转换为该类型。它可能并不完美,但我希望它能传达这个想法。

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

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

【讨论】:

  • 我不敢相信没有更好的方法来实现带有日期/时间的 JSON API。
  • 尽我所能点赞。它可以帮助某人知道这个拦截器需要注册才能工作。这个链接帮助我做到了:medium.com/@ryanchenkie_40935/…
  • 我还发现,在注册这个拦截器后,我的全局错误处理程序停止显示来自有效负载的错误消息,因为现在不解析错误响应。为了解决这个问题,我需要修改 intercept 方法的结尾,就像这样 return next.handle(req).catch((e, c) =&gt; { e.error = JSON.parse(e.error); return Observable.throw(e); }).map(event =&gt; { ... 这只是一个提示,显然可以改进以至少恢复日期。
  • @evilkos 好点。我添加了一个注册拦截器的例子。
  • @evilkos 您是说 HttpClient 正在为您将错误解析为 json 吗?我在他们的 onError 中看不到任何可以做到这一点的东西。有效载荷也可能是无效的 json。所以你可能想在你的全局错误处理程序中处理它。
【解决方案3】:

类似于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

【讨论】:

    【解决方案4】:

    你仍然可以,但你需要像这样从 rxjs 导入map()-operator:

    import 'rxjs/add/operator/map';
    

    然后,正如 Diego 指出的那样,您可以像这样使用 map

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

    【讨论】:

      【解决方案5】:

      你可以使用:

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

      rxjs 6+ 更新

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

      【讨论】:

      • 我收到错误Property 'map' does not exist on type 'Observable&lt;HttpResponse&lt;Object&gt;&gt;'.
      • 由于新版本的rxjs,您必须使用pipe()操作符。
      猜你喜欢
      • 2018-03-06
      • 1970-01-01
      • 2018-02-09
      • 1970-01-01
      • 1970-01-01
      • 2018-02-04
      • 1970-01-01
      • 2018-04-03
      • 1970-01-01
      相关资源
      最近更新 更多