You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中如何在类层面实现自定义JSON反序列化?

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 09:13:01