class-transformer
class-transformer copied to clipboard
feature: different strategy for nested transformations
Description
I would appreciate a feature that lets you switch the default expose-strategy for nested Objects.
Proposed solution
I'd like to define my Classes like:
class Child {
cAttr: number;
@Exclude()
invisible: number;
}
class Parent {
pAttr: number;
@ExposeNested() // this changes the default strategy for all sub-transformations to 'exposeAll'
child: Child;
}
This would instanceToPlain-transform something like:
| Default Strategy | Output | Description |
|---|---|---|
| exposeAll | { pAttr: 3, child: { cAttr: 7 } } |
just usual behavior |
| excludeAll | { child: { cAttr: 7 } } |
pAttr is excluded because default strategy cAttr is visible because Strategy changed by decoator |
The only dirty hack I found for this was to change the strategy by:
@Transform(p => { /* THIS IS BAD, DONT USE IT */
p.options.strategy = 'exposeAll';
return p.value;
})
but this will change the strategy not just for nested but for all following transformations, even in parent and so on.
Is there a not-so-hacky workarround for the time being?
Hello @mptr,
I am not sure on this. When would you need this?
Lets assume I use class-transformer to build Views for my REST-API. With the proposed mechanism i am able to use excludeAll as a default strategy for my views and am able to override it for nested fields if explicitly specified. For example my User class sould be excludeAll (exposes only explicitly annotated Fields) but associated Objects (for example Instances of "Post" class with the User as author) could be served using exposeAll which then does not require all Properties to be annotated.
I do think this as a low priority feature, so I do not think this will be implemented. Your "hacky" workaround seems to be good.
@mptr This works for me:
class Parent {
pAttr: number;
@Transform(transfromExposeAll())
child: Child;
}
/** Exposes all nested objects when strategy is set to `excludeAll` */
export const transfromExposeAll = () => {
@Expose()
class ExposeAll {}
@Expose()
class ExposeAllMap extends Map<unknown, unknown> {}
@Expose()
class ExposeAllSet extends Set<unknown> {}
const transform = (source: unknown): unknown => {
if (Array.isArray(source)) {
return source.map(transform);
} else if (source instanceof Set) {
return new ExposeAllSet(Array.from(source).map(transform));
} else if (source instanceof Map) {
return new ExposeAllMap(Array.from(source.entries()).map(([key, value]) => [key, transform(value)]));
} else if (typeof source === "object" && source !== null) {
return Object.assign(
new ExposeAll(),
Object.fromEntries(Object.entries(source).map(([key, value]) => [key, transform(value)])),
);
} else {
return source;
}
};
return ({ key, obj }: Pick<TransformFnParams, "key" | "obj">) => transform((obj as Record<string, unknown>)[key]);
};