如何防止我的对象的某些字段被类转换器转换

Posted

技术标签:

【中文标题】如何防止我的对象的某些字段被类转换器转换【英文标题】:How to prevent some fields of my object to be transformed by class-transformer 【发布时间】:2021-06-14 04:33:38 【问题描述】:

我正在尝试使用库 class-transformer(https://github.com/typestack/class-transformer) 将 typescript 类序列化为不接受海关类型的 firebase。

问题是某些数据实际上是与 firebase 兼容的数据,例如 GeoPoint 格式的“坐标”。

我已经序列化了这个类:

export abstract class StopStep extends Step 
  id: string;
  name: string;
  description: string;
  pointOfInterest: PointOfInterest | null;
  address: string;
  coordinates: firebase.firestore.GeoPoint;

我想防止“坐标”字段被转换。它应该存在,而不是未转换。

我尝试使用@Transform((value)=>...),但它似乎只是一个“附加”转换,它不允许我保持相同的格式。

我也尝试了Exclude,但该字段不再存在。

有没有办法用这个库来完成这个?

【问题讨论】:

【参考方案1】:

回答:我已经分析和调试了整个TransformOperationExecutor.ts,简单地说,没有内置方法可以抑制这个执行器尝试进行的无数转换。我想也许一起删除 @Type 装饰器可以实现目标。但即使缺少类型信息,该库仍然尝试尽可能地将复杂对象转换为普通对象。 @Transform((value)=>...) 不起作用的原因是,即使对于返回的值,也有多个检查来确定转换后的值是否需要任何进一步的转换才能“plain”。

解决方法: 我看到的唯一解决方案是自己构建该功能。我可以提供以下实现来模拟@Ignore 功能:

import  classToPlain, ClassTransformOptions, Transform, TransformFnParams  from 'class-transformer';

// New decorator to be used on members that should not be transformed.
export function Ignore() 
    // reuse transform decorator
    return Transform((params: TransformFnParams) => 
        if (params.type == TransformationType.CLASS_TO_PLAIN) 
            // class-transformer won't touch functions,
            // so we use function-objects as container to skip transformation.
            const container = () => params.value;
            container.type = '__skipTransformContainer__';

            return container;
        
        // On other transformations just return the value.
        return params.value;
    );


// Extended classToPlain to unwrap the container objects
export function customClassToPlain<T>(object: T, options?: ClassTransformOptions) 
    const result = classToPlain(object, options);
    unwrapSkipTransformContainers(result);
    return result;


// Recursive function to iterate over all keys of an object and its nested objects.
function unwrapSkipTransformContainers(obj: any) 
    for (const i in obj) 
        if (obj.hasOwnProperty(i)) 
            const currentValue = obj[i];
            if (currentValue?.type === '__skipTransformContainer__') 
                // retrieve the original value and also set it.
                obj[i] = currentValue();
                continue;
            

            // recursion + recursion anchor
            if (typeof currentValue === 'object') 
                unwrapSkipTransformContainers(currentValue);
            
        
    

此解决方案利用类转换器不会转换函数(get/set 函数除外),因为它们不是普通对象的一部分。我们重用 Transform 装饰器来包装我们不想用函数转换的成员。我们还用字符串__skipTransformContainer__ 标记这些函数,以便以后轻松找到它们,而不会意外调用错误的函数。然后我们编写一个新的customClassToPlain,它首先调用默认的classToPlain,然后在结果上调用递归解包函数。解包函数称为unwrapSkipTransformContainer,它递归搜索所有__skipTransformContainer__-containers 以检索未转换的值。

用法:

export abstract class StopStep extends Step 
  id: string;
  name: string;
  description: string;
  pointOfInterest: PointOfInterest | null;
  address: string;
  @Ignore()
  coordinates: firebase.firestore.GeoPoint;


class ExampleStep extends StopStep /*logic*/
    
const step = new ExampleStep();
const plain = customClassToPlain(step);

【讨论】:

我注意到的一点旁注:“class-transformer”的 classToPlain 类型声明被窃听。 classToPlain([obj1, obj2]) 返回类型 Record,但应该返回 Record[]. 感谢您的所有工作,看起来很棒!我明天必须测试一下。我想我还必须做一些自定义方法来转换回来(普通到打字稿)?如果这项工作有效,您认为应该对 repo 进行拉取请求吗? 功能请求可以,但不是拉取请求。如果您可以更改 TransformOperationExecutor,这将更容易实现。他们只需要添加一个额外的 else 分支并存储一些元数据。 对代码进行了更新,以支持普通到类。相反,它更容易,您不需要自定义方法。 @J4N 你能测试一下它是否适合你吗?【参考方案2】:

坏消息; Micro S. 是对的。即使您有自定义转换功能,“class-transformer”也会强制应用默认转换。这限制了自定义转换函数的使用,意味着“你不能返回任何你想要的”。

好消息; javascript 是一种高度灵活的语言。这意味着您可以覆盖类原型的任何函数。下面的代码在不损失性能的情况下修复了这个问题。

import isPlainObject from 'putil-isplainobject';

const oldTransformFn = TransformOperationExecutor.prototype.transform;
TransformOperationExecutor.prototype.transform =
  function transform(source: Record<string, any> | Record<string, any>[] | any,
                     value: Record<string, any> | Record<string, any>[] | any,
                     targetType: Function | TypeMetadata,
                     // eslint-disable-next-line @typescript-eslint/no-unused-vars
                     arrayType: Function,
                     // eslint-disable-next-line @typescript-eslint/no-unused-vars
                     isMap: boolean,
                     // eslint-disable-next-line @typescript-eslint/no-unused-vars
                     level?: number): any 
    // @ts-ignore
    if (isPlainObject(value) && targetType === Object) 
      return value;
    
    // @ts-ignore
    return oldTransformFn.apply(this, arguments);
  ;

【讨论】:

以上是关于如何防止我的对象的某些字段被类转换器转换的主要内容,如果未能解决你的问题,请参考以下文章

Spring对象映射器covertValue方法不转换某些字段

字段转换为 iPad 和 iPhone 上的电话号码链接

如何在将jsonObject转换为模型对象时检测缺失的字段

如何将 created_at 字段值从 Instagram 媒体对象转换为日期

如何在迁移后更改D7中某些字段的字段类型

如何防止写入特殊字符[重复]