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

如何为TypeScript属性创建@Relationship装饰器并解决反序列化问题

Let's tackle your two TypeScript challenges one by one with practical, hands-on solutions that fit your constraints:

1. Creating a @Relationship Decorator for TypeScript Properties

TypeScript property decorators let you attach metadata to class properties—perfect for marking ORM-style relationships. Here's a step-by-step implementation:

  • Step 1: Set up the decorator with metadata storage
    We'll use reflect-metadata (install it via npm install reflect-metadata, and enable experimentalDecorators + emitDecoratorMetadata in your tsconfig.json) to store relationship details.

    import 'reflect-metadata';
    
    // Unique key to identify relationship metadata
    const RELATIONSHIP_KEY = Symbol('relationship');
    
    // Decorator function (supports lazy model references to avoid circular imports)
    export function Relationship<T>(targetModel?: () => new () => T) {
      return (target: any, propertyKey: string | symbol) => {
        Reflect.defineMetadata(
          RELATIONSHIP_KEY,
          { targetModel },
          target,
          propertyKey
        );
      };
    }
    
  • Step 2: Mark properties with the decorator
    Use @Relationship to flag properties that represent related models:

    class User {
      id: number;
      name: string;
    
      @Relationship(() => Post) // Lazy reference to Post
      posts: Post[];
    }
    
    class Post {
      id: number;
      title: string;
    
      @Relationship(() => User) // Lazy reference to User
      author: User;
    }
    
  • Step 3: Retrieve metadata for processing
    When you need to handle relationships (like during deserialization), fetch the stored metadata:

    function getRelationshipMeta(target: any, prop: string) {
      return Reflect.getMetadata(RELATIONSHIP_KEY, target, prop);
    }
    
2. Initializing Result in a Generic Model Class Without a deserialize Method

Since you don't want to explicitly declare a deserialize method, we can leverage the base class constructor + our @Relationship metadata to auto-initialize the Result property when instantiating from JSON. Here's how:

  • Step 1: Build a generic base Model class
    This base class will handle property copying and relationship initialization automatically in its constructor:

    export abstract class Model<T> {
      constructor(data: Partial<T>) {
        // Copy basic enumerable properties first (matches your original deserialize behavior)
        Object.assign(this, data);
    
        // Process all relationship properties
        const prototype = Object.getPrototypeOf(this);
        const propNames = Object.getOwnPropertyNames(prototype);
    
        for (const prop of propNames) {
          const meta = getRelationshipMeta(prototype, prop);
          if (!meta?.targetModel) continue;
    
          const targetModel = meta.targetModel();
          const rawValue = (data as any)[prop];
    
          if (Array.isArray(rawValue)) {
            // Initialize array relationships (e.g., User.posts)
            (this as any)[prop] = rawValue.map(item => new targetModel(item));
          } else if (rawValue) {
            // Initialize single-object relationships (e.g., your Body.result)
            (this as any)[prop] = new targetModel(rawValue);
          }
        }
      }
    }
    
  • Step 2: Extend the base class for your models
    Your Body and Result classes just need to extend Model, and the Result property will auto-initialize when you create a Body instance from JSON:

    class Result extends Model<Result> {
      id: string;
      value: number;
    }
    
    class Body extends Model<Body> {
      id: number;
      name: string;
    
      @Relationship(() => Result)
      result: Result;
    }
    
    // Usage: Instantiate Body from JSON—Result is automatically initialized
    const jsonInput = { id: 1, name: "Sample", result: { id: "res-001", value: 42 } };
    const bodyInstance = new Body(jsonInput);
    
    console.log(bodyInstance.result instanceof Result); // Logs true
    console.log(bodyInstance.result.value); // Logs 42
    
  • Key benefits:

    • No separate deserialize method needed—the constructor handles all initialization logic.
    • Works with generic Model classes since we rely on runtime metadata rather than hardcoded type checks.
    • Handles both single-object and array relationships out of the box.

内容的提问来源于stack exchange,提问作者Filipe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:31:05