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

fix: Uint8Array are not being filled in plainToClassFromExist

Open vladbarcelo opened this issue 9 months ago • 2 comments

Description

Using plainToClassFromExist on classes with properties of type Uint8Array leads to data loss - the properties in the resulting class contain empty arrays.

Minimal code-snippet showcasing the problem

import { plainToClassFromExist, Type } from 'class-transformer';

class SomeClass {
  @Type(() => Uint8Array)
  public arr: Uint8Array;
}

const raw: SomeClass = {
  arr: new Uint8Array([1, 2, 3]),
};

console.log(raw, plainToClassFromExist(SomeClass, raw));

Expected behavior

{ arr: Uint8Array(3) [ 1, 2, 3 ] } [class SomeClass] { arr: Uint8Array(3) [ 1, 2, 3 ] }

Actual behavior

{ arr: Uint8Array(3) [ 1, 2, 3 ] } [class SomeClass] { arr: Uint8Array(0) [] }
                                                                       ^ data being lost

vladbarcelo avatar Mar 11 '25 16:03 vladbarcelo

Hi,

I'm encountering the same issue reported here where plainToInstance seems to incorrectly handle Uint8Array properties, causing the data to be lost during transformation.

Context:

We recently updated our project based on Prisma's recommendation (starting from v6) to use Uint8Array instead of Buffer for byte data, aligning with modern JavaScript runtimes.

Problem:

After migrating our entities to use Uint8Array for binary data fields, our unit tests involving class-transformer's plainToInstance started failing. Specifically, when transforming a plain object containing a Uint8Array property to a class instance, the resulting instance has an empty Uint8Array for that property, even though the input object contained valid data.

Minimal Reproduction Example:

Here's a simplified example demonstrating the issue:

Class Definition (TagEntity.ts):

import { Exclude, Expose } from 'class-transformer';
import { IsInt, IsString, Length } from 'class-validator'; // Assuming class-validator is also used

@Exclude()
export class TagEntity {
  @Expose()
  @IsString()
  @Length(1, 30)
  name: string;

  @Expose()
  nameBin: Uint8Array; // The property causing issues

  @Expose()
  @IsInt()
  id: number;
}

Transformation Code:

import { plainToInstance } from 'class-transformer';

// Assume 'tagObject' is the input data shown below
const transformedTag = plainToInstance(TagEntity, tagObject);
console.log(transformedTag);

Input Data (Object passed to plainToInstance):

const tagObject = {
  id: 2,
  uuid: 'fa07ce40-7fab-4dd2-9ba2-19727d76f600', // This property is excluded, which is fine
  name: 'テスト',
  nameBin: Uint8Array.from([ // Example: Creating Uint8Array from an array
    227, 131, 134,
    227, 130, 185,
    227, 131, 136
  ]),
  description: null, // Excluded
  createdAt: new Date('2025-04-19T04:29:49.000Z'), // Excluded
  updatedAt: new Date('2025-04-19T04:29:49.000Z')  // Excluded
};

Actual Result (Output from plainToInstance):

TagEntity { name: 'テスト', nameBin: Uint8Array(0) [], id: 2 }
// Note: nameBin is an empty Uint8Array

Expected Result:

The nameBin property in the resulting TagEntity instance should contain the same Uint8Array data as the input object.

TagEntity {
  name: 'テスト',
  nameBin: Uint8Array(9) [
    227, 131, 134,
    227, 130, 185,
    227, 131, 136
  ],
  id: 2
}

Could you please investigate this behavior? Thank you.

takakikasuga avatar Apr 19 '25 04:04 takakikasuga

Hi @takakikasuga,

Have you found a workaround for this issue?

OmMahen avatar Sep 03 '25 03:09 OmMahen