Angular 自动序列化/反序列化装饰器(Demo)

80 阅读3分钟

下面我将为你实现一个完整的 Angular 自动序列化/反序列化装饰器,它可以处理类与普通对象之间的转换,支持嵌套对象、日期处理和自定义转换逻辑。

完整代码实现

1. 首先创建核心装饰器和工具函数

typescript

// serializable.decorator.ts

export interface SerializableConfig {
  alias?: string;
  customConverter?: (value: any) => any;
  type?: any;
}

// 缓存元数据
const serializableMetadataKey = Symbol('serializable:metadata');

export function Serializable(config: SerializableConfig = {}): PropertyDecorator {
  return (target: any, propertyKey: string | symbol) => {
    const metadata = Reflect.getMetadata(serializableMetadataKey, target) || {};
    
    metadata[propertyKey] = {
      alias: config.alias || propertyKey,
      customConverter: config.customConverter,
      type: config.type
    };
    
    Reflect.defineMetadata(serializableMetadataKey, metadata, target);
  };
}

export function getSerializableMetadata(target: any): {[key: string]: SerializableConfig} {
  return Reflect.getMetadata(serializableMetadataKey, target) || {};
}

// 处理不同类型的转换
function convertValue(value: any, type: any): any {
  if (value === null || value === undefined) {
    return value;
  }

  // 处理数组
  if (type && type.prototype && Array.isArray(type.prototype)) {
    return value.map((item: any) => convertValue(item, type));
  }

  // 处理日期
  if (type === Date) {
    return new Date(value);
  }

  // 处理嵌套对象
  if (typeof type === 'function' && type.prototype && type.prototype.constructor) {
    return deserialize(value, type);
  }

  return value;
}

// 反序列化函数
export function deserialize<T>(json: any, type: new () => T): T {
  if (!json) {
    return json;
  }

  const instance = new type();
  const metadata = getSerializableMetadata(instance);

  Object.keys(metadata).forEach(propertyKey => {
    const config = metadata[propertyKey];
    const jsonKey = config.alias;
    const value = json[jsonKey];

    if (value !== undefined) {
      if (config.customConverter) {
        instance[propertyKey] = config.customConverter(value);
      } else if (config.type) {
        instance[propertyKey] = convertValue(value, config.type);
      } else {
        instance[propertyKey] = value;
      }
    }
  });

  return instance;
}

// 序列化函数
export function serialize(instance: any): any {
  if (!instance) {
    return instance;
  }

  const result: any = {};
  const metadata = getSerializableMetadata(instance);

  Object.keys(metadata).forEach(propertyKey => {
    const config = metadata[propertyKey];
    const value = instance[propertyKey];
    
    if (value !== undefined) {
      if (config.customConverter) {
        result[config.alias] = config.customConverter(value);
      } else if (value instanceof Date) {
        result[config.alias] = value.toISOString();
      } else if (Array.isArray(value)) {
        result[config.alias] = value.map(item => serialize(item));
      } else if (typeof value === 'object' && value !== null) {
        result[config.alias] = serialize(value);
      } else {
        result[config.alias] = value;
      }
    }
  });

  return result;
}

2. 创建测试模型和使用示例

typescript

// user.model.ts

import { Serializable, deserialize, serialize } from './serializable.decorator';

class Address {
  @Serializable()
  street: string;

  @Serializable()
  city: string;

  @Serializable({ alias: 'zip_code' })
  zipCode: string;
}

class User {
  @Serializable({ alias: 'user_id' })
  id: number;

  @Serializable()
  name: string;

  @Serializable({ alias: 'created_at', type: Date })
  createdAt: Date;

  @Serializable({ type: Address })
  address: Address;

  @Serializable({ 
    alias: 'is_active',
    customConverter: (value: any) => Boolean(value)
  })
  isActive: boolean;

  @Serializable({ type: [String] })
  tags: string[];
}

// 使用示例
const jsonData = {
  user_id: 123,
  name: 'John Doe',
  created_at: '2023-05-15T10:00:00Z',
  address: {
    street: '123 Main St',
    city: 'New York',
    zip_code: '10001'
  },
  is_active: 1,
  tags: ['premium', 'vip']
};

// 反序列化
const user = deserialize(jsonData, User);
console.log(user);
/*
User {
  id: 123,
  name: 'John Doe',
  createdAt: Date,
  address: Address {
    street: '123 Main St',
    city: 'New York',
    zipCode: '10001'
  },
  isActive: true,
  tags: ['premium', 'vip']
}
*/

// 序列化
const serializedUser = serialize(user);
console.log(serializedUser);
/*
{
  user_id: 123,
  name: 'John Doe',
  created_at: '2023-05-15T10:00:00.000Z',
  address: {
    street: '123 Main St',
    city: 'New York',
    zip_code: '10001'
  },
  is_active: true,
  tags: ['premium', 'vip']
}
*/

3. 在 Angular 服务中使用

typescript

// user.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { deserialize, serialize } from './serializable.decorator';
import { User } from './user.model';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  constructor(private http: HttpClient) {}

  getUser(id: number) {
    return this.http.get(`/api/users/${id}`).pipe(
      map(response => deserialize(response, User))
    );
  }

  updateUser(user: User) {
    return this.http.put(`/api/users/${user.id}`, serialize(user));
  }
}

功能说明

  1. @Serializable 装饰器

    • alias: 指定 JSON 中的属性名与类属性名不同时的映射
    • type: 指定属性的类型,用于复杂类型的自动转换
    • customConverter: 自定义转换函数
  2. 支持的类型

    • 基本类型 (string, number, boolean)
    • 日期对象 (自动与 ISO 字符串转换)
    • 嵌套对象
    • 数组
    • 自定义类型
  3. 主要函数

    • deserialize(): 将普通对象转换为类实例
    • serialize(): 将类实例转换为普通对象

使用建议

  1. 在 Angular 项目中,可以将此装饰器用于 API 返回的数据模型

  2. 在 HTTP 拦截器中自动处理序列化/反序列化

  3. 对于大型项目,可以考虑进一步扩展支持:

    • 循环引用处理
    • 更复杂的自定义验证
    • 继承关系的处理

这个实现提供了完整的类型安全性和灵活的配置选项,可以满足大多数 Angular 项目中对象序列化的需求。

关联文章

TypeScript 中的 Metadata(元数据)详解

Typescript的装饰器简介