class-transformer icon indicating copy to clipboard operation
class-transformer copied to clipboard

feature: different strategy for nested transformations

Open mptr opened this issue 3 years ago • 4 comments
trafficstars

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?

mptr avatar Apr 16 '22 18:04 mptr

Hello @mptr,

I am not sure on this. When would you need this?

diffy0712 avatar May 16 '24 18:05 diffy0712

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.

mptr avatar May 20 '24 15:05 mptr

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.

diffy0712 avatar May 22 '24 21:05 diffy0712

@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]);
};

aravindanve avatar Dec 23 '24 11:12 aravindanve