如何防止我的对象的某些字段被 class-transformer 转换
How to prevent some fields of my object to be transformed by class-transformer
我正在尝试使用库 class-transformer
(https://github.com/typestack/class-transformer) 将打字稿 classes 序列化为 firebase,它不接受自定义类型。
问题是一些数据实际上是与 firebase 兼容的数据,例如 GeoPoint 格式的“坐标”。
我已经将这个 class 序列化了:
export abstract class StopStep extends Step {
id: string;
name: string;
description: string;
pointOfInterest: PointOfInterest | null;
address: string;
coordinates: firebase.firestore.GeoPoint;
}
我想阻止“坐标”字段被转换。它应该存在,而不是未转化。
我尝试使用 @Transform(({value})=>...)
,但它似乎只是一个“附加”转换,它不允许我只保留相同的格式。
我也尝试过 Exclude
,但该字段不再存在。
有没有办法用这个库来完成这个?
回答:我已经分析和调试了整个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);
}
}
}
}
此解决方案利用 class-transformer 不会转换函数(get/set 函数除外),因为它们不是普通对象的一部分。我们重用 Transform
装饰器来包装我们不想用函数转换的成员。我们还用字符串 __skipTransformContainer__
标记这些函数,以便以后轻松找到它们,而不会意外调用错误的函数。然后我们写一个新的 customClassToPlain
,它首先调用默认的 classToPlain
,然后对结果调用递归解包函数。展开函数称为 unwrapSkipTransformContainer
并递归搜索所有 __skipTransformContainer__
容器以检索未转换的值。
用法:
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);
坏消息;Micro S. 是对的。 “class-transformer”强制应用默认转换,即使您有自定义转换功能。这限制了自定义转换功能的使用,意味着“你不能 return 你想要的任何东西”。
好消息;JavaScript是一门高度灵活的语言。这意味着您可以覆盖 class 原型的任何函数。下面的代码在不影响性能的情况下解决了这个问题。
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);
};
我正在尝试使用库 class-transformer
(https://github.com/typestack/class-transformer) 将打字稿 classes 序列化为 firebase,它不接受自定义类型。
问题是一些数据实际上是与 firebase 兼容的数据,例如 GeoPoint 格式的“坐标”。
我已经将这个 class 序列化了:
export abstract class StopStep extends Step {
id: string;
name: string;
description: string;
pointOfInterest: PointOfInterest | null;
address: string;
coordinates: firebase.firestore.GeoPoint;
}
我想阻止“坐标”字段被转换。它应该存在,而不是未转化。
我尝试使用 @Transform(({value})=>...)
,但它似乎只是一个“附加”转换,它不允许我只保留相同的格式。
我也尝试过 Exclude
,但该字段不再存在。
有没有办法用这个库来完成这个?
回答:我已经分析和调试了整个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);
}
}
}
}
此解决方案利用 class-transformer 不会转换函数(get/set 函数除外),因为它们不是普通对象的一部分。我们重用 Transform
装饰器来包装我们不想用函数转换的成员。我们还用字符串 __skipTransformContainer__
标记这些函数,以便以后轻松找到它们,而不会意外调用错误的函数。然后我们写一个新的 customClassToPlain
,它首先调用默认的 classToPlain
,然后对结果调用递归解包函数。展开函数称为 unwrapSkipTransformContainer
并递归搜索所有 __skipTransformContainer__
容器以检索未转换的值。
用法:
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);
坏消息;Micro S. 是对的。 “class-transformer”强制应用默认转换,即使您有自定义转换功能。这限制了自定义转换功能的使用,意味着“你不能 return 你想要的任何东西”。
好消息;JavaScript是一门高度灵活的语言。这意味着您可以覆盖 class 原型的任何函数。下面的代码在不影响性能的情况下解决了这个问题。
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);
};