web-dev-qa-db-ja.com

Angular 4.3 HttpClientで日付を解析する

現在、Angular 4.3の新しいHttpClientに切り替えています。1つの利点は、GETメソッドで型情報を指定でき、返されたJSONが指定された型に解析されることです。

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

ただし、残念ながら、JSONのすべての日付は、結果のオブジェクトの数値として解析されます(おそらく、Java Dateオブジェクトはバックエンドで数値としてシリアル化されるため)。

古いHttpでは、次のようにJSON.parse()を呼び出すときにリバイバー関数を使用しました。

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

そして、リバイバー機能では、数字から日付オブジェクトを作成しました:

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

  return value;
}

新しいHttpClientに同様のメカニズムがありますか?または、JSONが解析されたときに変換を行うためのベストプラクティスは何ですか?

20
Ralf Schneider

残念ながら、Angular HttpClient内で使用されるJSON.parseメソッドにリバイバーを渡す方法はないようです。ここでは、JSON.parseを呼び出す場所のソースコードを示します。 https://github.com/angular/angular/blob/20e1cc049fa632e88dfeb00476455a41b7f42338/packages/common/http/src/xhr.ts#L189

Angularは、応答タイプが「json」に設定されている場合にのみ応答を解析します(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

18
bygrace

これは私のために働く:

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 を変更しました。配列をサポートする必要があります。

14
Lerner

それでも可能ですが、次のようにrxjsからmap()- operatorをインポートする必要があります。

import 'rxjs/add/operator/map';

その後、ディエゴが指摘したように、次のようにmapを使用できます。

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

次を使用できます。

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

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で導入されました。インポート処理、コードの可読性を促進し、バンドルサイズを削減します。 Operator Importsの理解 を参照してください。

3
Markus Pscheidt