Angular 13中如何映射JSON-LD/Hydra响应为类型化分页对象
问题原因
Hydra 规范返回的集合字段带hydra:命名空间前缀,和你前端定义的List<T>接口字段名不匹配:
- 你定义的
totalItems对应接口返回的hydra:totalItems - 你定义的
members对应接口返回的hydra:member
Angular HttpClient 不会自动做字段名映射,手动转换响应结构即可,以下是两种可直接落地的实现方案:
方案1:单接口手动映射(适合少量Hydra接口场景)
- 先修正
List<T>接口的类型错误,接口返回的hydra:totalItems是数值类型,不是字符串:
export interface List<T> { totalItems: number; members: T[]; }
- 修改
CompanyService的get()方法,通过RxJS的map操作符转换响应结构,同时修正返回值为强类型:
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { map, Observable } from 'rxjs'; import { List } from './list.interface'; import { CompanyModel } from './company.model'; @Injectable({ providedIn: 'root' }) export class CompanyService { endpoint = '你的接口地址前缀'; // 替换成你实际的接口地址配置 constructor(private http: HttpClient) {} get(): Observable<List<CompanyModel>> { return this.http.get<any>(`${this.endpoint}/companies`, { headers: new HttpHeaders({ 'Accept': 'application/ld+json' }) }).pipe( map(res => ({ totalItems: res['hydra:totalItems'], members: res['hydra:member'].map((item: any) => new CompanyModel( item.id, item.companyName, item.logoUrl, item.users )) } as List<CompanyModel>)) ); } }
注意:
CompanyModel是类而非TS接口,HTTP反序列化默认返回普通JS对象,不会自动生成类实例,如果需要用到类的自定义方法,必须手动调用构造函数生成实例;如果仅做类型约束,把CompanyModel改成interface可以省去手动构造实例的步骤。
方案2:全局拦截器统一映射(适合项目全量使用Hydra接口场景)
如果项目内大部分接口都遵循Hydra规范,不用每个接口重复写转换逻辑,实现一个全局HTTP拦截器统一处理所有Hydra集合响应即可:
- 生成拦截器文件:
ng generate interceptor hydra-mapping
- 编写拦截器转换逻辑:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpResponse } from '@angular/common/http'; import { map, Observable } from 'rxjs'; @Injectable() export class HydraMappingInterceptor implements HttpInterceptor { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { return next.handle(request).pipe( map(event => { // 仅标记为hydra:Collection的集合响应做转换 if (event instanceof HttpResponse && event.body?.['@type'] === 'hydra:Collection') { const rawBody = event.body; const mappedBody = { totalItems: rawBody['hydra:totalItems'], members: rawBody['hydra:member'], // 如需分页导航信息,可保留hydra:view字段,扩展List接口接收即可 view: rawBody['hydra:view'] }; return event.clone({ body: mappedBody }); } return event; }) ); } }
- 在根模块
app.module.ts中注册拦截器:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { HydraMappingInterceptor } from './hydra-mapping.interceptor'; import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, HttpClientModule], providers: [ { provide: HTTP_INTERCEPTORS, useClass: HydraMappingInterceptor, multi: true } ], bootstrap: [AppComponent] }) export class AppModule { }
拦截器注册完成后,所有Hydra集合响应会被自动转换成List<T>对应的字段结构,Service层可以直接写强类型请求,不需要额外转换:
get(): Observable<List<CompanyModel>> { return this.http.get<List<CompanyModel>>(`${this.endpoint}/companies`, { headers: new HttpHeaders({ 'Accept': 'application/ld+json' }) }); }
扩展建议
如果需要实现完整分页逻辑,可以扩展List<T>接口,接收hydra:view中返回的首页、下一页、上一页、末页链接,不用额外解析参数。
内容的提问来源于stack exchange,提问作者user2879055
相关产品推荐
相关产品推荐

