TypeScript中如何在类层面实现自定义JSON反序列化?
我完全明白你的需求——通过值对象来规避原始类型痴迷问题,确保只有格式合规的ID才能在代码中传递,同时还希望JSON的序列化和反序列化能自动适配这些自定义类型,不用每次手动折腾转换逻辑。下面我给你几个实用的方案,覆盖通用TypeScript场景和Angular专属的实现方式:
方案一:静态工厂方法 + JSON.parse 复活器(通用TypeScript方案)
首先,你原来写的fromJSON作为实例方法不太合理,因为反序列化是要从字符串生成新实例,而不是基于已有实例修改。我们可以把它改成静态工厂方法,再配合JSON.parse的reviver参数来自动转换:
修改后的ExampleId类
export class ExampleId { private _value: string; constructor(value: string) { if (!this.isValid(value)) { throw new Error(`Invalid project status id '${value}'`); } this._value = value; } public get value(): string { return this._value; } private isValid(value: string): boolean { return /^example_[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?4[0-9A-Fa-f]{3}-?[89ABab][0-9A-Fa-f]{3}-?[0-9A-Fa-f]{12}$/.test(value); } // 序列化时自动返回底层字符串,适配JSON.stringify private toJSON() { return this.value; } // 改成静态方法,用于从字符串生成ExampleId实例 public static fromJSON(input: string): ExampleId { return new ExampleId(input); } }
使用方式
在解析JSON的时候,通过reviver函数判断字段是否需要转换为ExampleId:
const jsonString = '{"id": "example_9B75ECBC-5DA8-4821-B9DF-2EF5DE9597F5", "otherData": "test"}'; const parsedData = JSON.parse(jsonString, (key, value) => { // 可以根据字段名或者值格式来判断转换逻辑 if (key === 'id' && typeof value === 'string' && value.startsWith('example_')) { return ExampleId.fromJSON(value); } return value; }); // 此时parsedData.id已经是ExampleId类型,可以直接传入ExampleResponse const exampleResponse = new ExampleResponse(parsedData.id, parsedData.otherData);
这个方案不需要额外依赖,灵活可控,但需要手动处理reviver逻辑,适合小型项目或不想引入第三方库的场景。
方案二:Angular HttpClient 拦截器(Angular专属)
如果你在Angular项目中使用HttpClient,可以通过拦截器全局处理反序列化,这样所有接口返回的符合格式的ID都会自动转换成ExampleId实例,不用每次手动处理:
实现反序列化拦截器
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ExampleId } from './example-id'; @Injectable() export class DeserializationInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req).pipe( map(event => { if (event instanceof HttpResponse) { // 递归转换响应体中的字段 const transformedBody = this.transformResponseBody(event.body); return event.clone({ body: transformedBody }); } return event; }) ); } private transformResponseBody(body: any): any { if (typeof body === 'object' && body !== null) { // 遍历对象所有属性 for (const key in body) { if (Object.prototype.hasOwnProperty.call(body, key)) { const value = body[key]; // 匹配ExampleId格式的字符串,自动转换为实例 if (typeof value === 'string' && /^example_[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?4[0-9A-Fa-f]{3}-?[89ABab][0-9A-Fa-f]{3}-?[0-9A-Fa-f]{12}$/.test(value)) { body[key] = new ExampleId(value); } else if (typeof value === 'object') { // 递归处理嵌套对象/数组 this.transformResponseBody(value); } } } } return body; } }
注册拦截器
在你的AppModule中注册这个拦截器,让它全局生效:
import { NgModule } from '@angular/core'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { DeserializationInterceptor } from './deserialization.interceptor'; @NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: DeserializationInterceptor, multi: true } ] }) export class AppModule { }
使用效果
现在你用HttpClient请求接口时,返回的ExampleResponse中的id会自动是ExampleId类型:
this.http.get<ExampleResponse>('/api/example').subscribe(response => { // response.id 已经是ExampleId实例,可以直接调用value属性 console.log(response.id.value); });
这个方案的优势是全局自动处理,不用在每个请求里重复写转换逻辑,非常适合Angular项目中多个地方需要反序列化的场景。
方案三:使用类转换库(通用TS/Angular)
如果你的项目比较复杂,有很多自定义类型需要处理,可以使用现成的类转换库,比如class-transformer,它通过装饰器来配置序列化和反序列化规则,非常便捷。
安装依赖
npm install class-transformer reflect-metadata
配置类装饰器
import { Transform, Type } from 'class-transformer'; import 'reflect-metadata'; export class ExampleId { private _value: string; constructor(value: string) { if (!this.isValid(value)) { throw new Error(`Invalid project status id '${value}'`); } this._value = value; } public get value(): string { return this._value; } private isValid(value: string): boolean { return /^example_[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?4[0-9A-Fa-f]{3}-?[89ABab][0-9A-Fa-f]{3}-?[0-9A-Fa-f]{12}$/.test(value); } } // 为ExampleResponse配置转换规则 export class ExampleResponse { // 指定id字段需要转换为ExampleId类型 @Type(() => ExampleId) @Transform(({ value }) => new ExampleId(value), { toClassOnly: true }) id: ExampleId; otherData: any; constructor(id: ExampleId, otherData: any) { this.id = id; this.otherData = otherData; } }
使用方式
用plainToClass方法把JSON对象转换成类实例:
import { plainToClass } from 'class-transformer'; const jsonData = { id: 'example_9B75ECBC-5DA8-4821-B9DF-2EF5DE9597F5', otherData: 'test' }; const exampleResponse = plainToClass(ExampleResponse, jsonData); // exampleResponse.id 是ExampleId类型
在Angular中,可以结合HttpClient的map操作符使用:
import { plainToClass } from 'class-transformer'; this.http.get('/api/example').pipe( map(data => plainToClass(ExampleResponse, data)) ).subscribe(response => { console.log(response.id.value); });
这个方案功能强大,支持复杂的嵌套类型、数组转换等场景,适合大型项目,能减少大量重复的转换代码。
内容来源于stack exchange

