Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: ability to @Transform absent properties in plainToInstance #1813

Open
blended-bram opened this issue Feb 25, 2025 · 2 comments
Open
Labels
flag: needs discussion Issues which needs discussion before implementation. type: feature Issues related to new features.

Comments

@blended-bram
Copy link

Description

There currently is no way to decorate a property with a factory to produce a value for that field.
Specifically, to fill a field that is absent based on other data inside the object.

While you might get around that to some extend with computed properties, but these properties are not mutable without some serious plumbing to enable that.

Example case:

class Subject {
  @Transform(({value}) => value ?? 0) // (1)
  field!: number;
}

const instance = plainToInstance(Subject, {}) // (2)

Here (1) provides a fallback value for a field should it be blank (present, but undefined / null).
But counterintutively, an absent field and undefined are treated as separate cases; which runs counter to most JS code which treats absent == undefined. Only the in operator would help you spot the difference normally, or inspecting the entries.

Proposed solution

Without breaking backwards compatibility; add a flag to @Transform that causes the transform to trigger if the field is absent. The value property of TransformFnParams would logically be undefined.

One might argue that this does not belong to Transform because there is no value to be transformed and that this should become a separate decorator. But this will create a rift where users must be aware of the obscure difference between {field: undefined} and {}.

Please extend @transform with an option flag that opts users into running the transform on absent fields.
The field is akin to the to{Class,Plain}Only options already available, as it controls in what scenarios the transform is applied. So the new option could be called fromAbsentAlso.

class User {
  id!: number;

  // 1. Avatar can be specified in the source.
  // 2. The fallback is constructed from sibling data.
  // 3. The instance field is mutable and not some convoluted computed property.
  @Transform(({ value, obj }) => value ?? `https://example.com/${obj.id}/avatar.png`, {fromAbsentAlso: true})
  avatar!: string;
}

With this @Transform is providing a fallback for absent data; that logically treats absent fields as if they were explicitly set to undefined

As a real-world example, we have need for this when parsing data from Sanity.io, where their asset (image/video) objects do not yet have a usable url property when retrieved. Instead you can generate the url by building it from the other fields in the object. This is where we want to automatically build the url when parsing the data structure, but the field url does not yet exist in the raw data we receive.

@blended-bram blended-bram added flag: needs discussion Issues which needs discussion before implementation. type: feature Issues related to new features. labels Feb 25, 2025
@blended-bram
Copy link
Author

Currently the @Transform is applied at

finalValue = this.applyCustomTransformations(
, which sits in a loop over the available properties in the source object
for (const key of keys) {
.

Most straightforward is to loop over all properties of the class, filter out those already done through keys, filter out those that do not opt-in to the new functionality and then run those transforms.

@blended-bram
Copy link
Author

While working on a patch to introduce this; I noticed that adding @Expose() changes the behaviour or @Transform and causes it to work on for properties not present in the source object. Which is strange because the default strategy is expose-all, so why the obscure difference?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flag: needs discussion Issues which needs discussion before implementation. type: feature Issues related to new features.
Development

No branches or pull requests

1 participant