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