reflect-metadata vs class-transformer vs class-validator
TypeScript Decorators and Validation Libraries Comparison
1 Year
reflect-metadataclass-transformerclass-validatorSimilar Packages:
What's TypeScript Decorators and Validation Libraries?

These libraries are essential for enhancing TypeScript applications, particularly in the context of object-oriented programming. They provide powerful tools for transforming and validating class instances, which is crucial for ensuring data integrity and consistency in applications. By leveraging decorators and metadata reflection, developers can create robust data models that are easy to manage and validate, making them particularly useful in frameworks like NestJS and for building APIs.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
reflect-metadata15,411,1463,260241 kB25a year agoApache-2.0
class-transformer5,106,7577,097776 kB2443 years agoMIT
class-validator4,169,37211,3325.12 MB302a year agoMIT
Feature Comparison: reflect-metadata vs class-transformer vs class-validator

Data Transformation

  • reflect-metadata:

    reflect-metadata provides the necessary infrastructure to support metadata reflection in TypeScript. It allows class-transformer and class-validator to function correctly by enabling decorators to store and retrieve metadata about class properties.

  • class-transformer:

    class-transformer allows you to easily convert plain objects into class instances and vice versa. This is particularly useful when dealing with data from APIs, as it enables you to instantiate classes with methods and properties, enhancing the usability of your data models.

  • class-validator:

    class-validator does not focus on data transformation but can work alongside class-transformer to ensure that the transformed data adheres to specific validation rules. It ensures that once data is transformed, it meets the required criteria before further processing.

Validation Capabilities

  • reflect-metadata:

    reflect-metadata does not provide validation capabilities but is crucial for enabling the validation decorators in class-validator to function properly by allowing them to access metadata about class properties.

  • class-transformer:

    class-transformer does not provide validation capabilities on its own; it focuses on transformation. However, it can be combined with class-validator to ensure that transformed data is also validated, creating a robust data handling mechanism.

  • class-validator:

    class-validator is specifically designed for validation, offering a rich set of decorators for various validation rules. It allows you to enforce constraints on class properties, ensuring that the data is valid before it is used in your application.

Decorator Support

  • reflect-metadata:

    reflect-metadata is essential for enabling the use of decorators in TypeScript. It allows decorators to store metadata about classes and their properties, which is crucial for both class-transformer and class-validator to function effectively.

  • class-transformer:

    class-transformer utilizes decorators to define how properties should be transformed. This makes it easy to annotate class properties with transformation rules, enhancing code readability and maintainability.

  • class-validator:

    class-validator heavily relies on decorators to apply validation rules to class properties. This declarative approach allows developers to easily see and understand the validation logic associated with each property, improving code clarity.

Integration

  • reflect-metadata:

    reflect-metadata is a foundational library that supports the functionality of both class-transformer and class-validator. It is necessary for enabling the decorator functionality that both libraries depend on for their operations.

  • class-transformer:

    class-transformer integrates seamlessly with class-validator, allowing you to transform and validate data in a cohesive manner. This integration is particularly beneficial in frameworks like NestJS, where both transformation and validation are often required.

  • class-validator:

    class-validator works best when used in conjunction with class-transformer, as it can validate the transformed data. This integration ensures that your data models are not only correctly structured but also valid according to your business rules.

Learning Curve

  • reflect-metadata:

    reflect-metadata has a low learning curve as it primarily serves as a utility library. Understanding its role in enabling decorators is essential, but it does not require extensive knowledge to implement.

  • class-transformer:

    class-transformer has a moderate learning curve, especially for developers familiar with object-oriented programming and TypeScript. Understanding how to effectively use decorators and transformation rules is key to leveraging its full potential.

  • class-validator:

    class-validator is relatively easy to learn, especially for those who are already familiar with TypeScript and decorators. Its straightforward API and rich set of built-in validators make it accessible for developers of varying skill levels.

How to Choose: reflect-metadata vs class-transformer vs class-validator
  • reflect-metadata:

    Choose reflect-metadata if you need to utilize TypeScript's decorator metadata capabilities. This package is essential for enabling decorators to work effectively, especially in conjunction with class-transformer and class-validator, as it allows for the storage and retrieval of metadata associated with classes and their properties.

  • class-transformer:

    Choose class-transformer if you need to transform plain JavaScript objects into class instances and vice versa. It is particularly useful when working with APIs where you need to map JSON data to TypeScript classes, allowing you to leverage class methods and properties directly.

  • class-validator:

    Choose class-validator if your application requires robust validation of data models. It provides a wide range of built-in validation decorators that can be easily applied to class properties, ensuring that the data adheres to specified rules before processing or saving it.

README for reflect-metadata

Metadata Reflection API

NOTE: Now that both Decorators and Decorator Metadata have achieved Stage 3 within TC39, the API proposed below is no longer being considered for standardization. However, this package will continue to support projects that leverage TypeScript's legacy --experimentalDecorators option as some projects may not be able to migrate to use standard decorators.

Installation

npm install reflect-metadata

Usage

ES Modules in NodeJS/Browser, TypeScript/Babel, Bundlers

// - Modifies global `Reflect` object (or defines one in ES5 runtimes).
// - Supports ESM and CommonJS.
// - Contains internal polyfills for `Map`, `Set`, and `WeakMap` for older runtimes.
import "reflect-metadata";

// - Modifies global `Reflect` object (or defines one in ES5 runtimes).
// - Supports ESM and CommonJS.
// - Requires runtime support for `"exports"` in `package.json`.
// - Does not include internal polyfills.
import "reflect-metadata/lite";

CommonJS

// - Modifies global `Reflect` object (or defines one in ES5 runtimes).
// - Contains internal polyfills for `Map`, `Set`, and `WeakMap` for older runtimes.
require("reflect-metadata");

// - Modifies global `Reflect` object (or defines one in ES5 runtimes).
// - Requires runtime support for `"exports"` in `package.json`.
// - Does not include internal polyfills.
require("reflect-metadata/lite");

In the Browser via <script>

HTML

<!-- Modifies global `Reflect` object (or defines one in ES5 runtimes). -->
<!-- Contains internal polyfills for `Map`, `Set`, and `WeakMap` for older runtimes. -->
<script src="path/to/reflect-metadata/Reflect.js"></script>

<!-- Modifies global `Reflect` object (or defines one in ES5 runtimes). -->
<!-- Does not include internal polyfills. -->
<script src="path/to/reflect-metadata/ReflectLite.js"></script>

Script

// - Makes types available in your editor.
/// <reference path="path/to/reflect-metadata/standalone.d.ts" />

Background

  • Decorators add the ability to augment a class and its members as the class is defined, through a declarative syntax.
  • Traceur attaches annotations to a static property on the class.
  • Languages like C# (.NET), and Java support attributes or annotations that add metadata to types, along with a reflective API for reading metadata.

Goals

  • A number of use cases (Composition/Dependency Injection, Runtime Type Assertions, Reflection/Mirroring, Testing) want the ability to add additional metadata to a class in a consistent manner.
  • A consistent approach is needed for various tools and libraries to be able to reason over metadata.
  • Metadata-producing decorators (nee. "Annotations") need to be generally composable with mutating decorators.
  • Metadata should be available not only on an object but also through a Proxy, with related traps.
  • Defining new metadata-producing decorators should not be arduous or over-complex for a developer.
  • Metadata should be consistent with other language and runtime features of ECMAScript.

Syntax

  • Declarative definition of metadata:
class C {
  @Reflect.metadata(metadataKey, metadataValue)
  method() {
  }
}
  • Imperative definition of metadata:
Reflect.defineMetadata(metadataKey, metadataValue, C.prototype, "method");
  • Imperative introspection of metadata:
let obj = new C();
let metadataValue = Reflect.getMetadata(metadataKey, obj, "method");

Semantics

  • Object has a new [[Metadata]] internal property that will contain a Map whose keys are property keys (or undefined) and whose values are Maps of metadata keys to metadata values.
  • Object will have a number of new internal methods for [[DefineOwnMetadata]], [[GetOwnMetadata]], [[HasOwnMetadata]], etc.
    • These internal methods can be overridden by a Proxy to support additional traps.
    • These internal methods will by default call a set of abstract operations to define and read metadata.
  • The Reflect object will expose the MOP operations to allow imperative access to metadata.
  • Metadata defined on class declaration C is stored in C.[[Metadata]], with undefined as the key.
  • Metadata defined on static members of class declaration C are stored in C.[[Metadata]], with the property key as the key.
  • Metadata defined on instance members of class declaration C are stored in C.prototype.[[Metadata]], with the property key as the key.

API

// define metadata on an object or property
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

// check for presence of a metadata key on the prototype chain of an object or property
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);

// check for presence of an own metadata key of an object or property
let result = Reflect.hasOwnMetadata(metadataKey, target);
let result = Reflect.hasOwnMetadata(metadataKey, target, propertyKey);

// get metadata value of a metadata key on the prototype chain of an object or property
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);

// get metadata value of an own metadata key of an object or property
let result = Reflect.getOwnMetadata(metadataKey, target);
let result = Reflect.getOwnMetadata(metadataKey, target, propertyKey);

// get all metadata keys on the prototype chain of an object or property
let result = Reflect.getMetadataKeys(target);
let result = Reflect.getMetadataKeys(target, propertyKey);

// get all own metadata keys of an object or property
let result = Reflect.getOwnMetadataKeys(target);
let result = Reflect.getOwnMetadataKeys(target, propertyKey);

// delete metadata from an object or property
let result = Reflect.deleteMetadata(metadataKey, target);
let result = Reflect.deleteMetadata(metadataKey, target, propertyKey);

// apply metadata via a decorator to a constructor
@Reflect.metadata(metadataKey, metadataValue)
class C {
  // apply metadata via a decorator to a method (property)
  @Reflect.metadata(metadataKey, metadataValue)
  method() {
  }
}

Alternatives

  • Use properties rather than a separate API.
    • Obvious downside is that this can be a lot of code:
function ParamTypes(...types) {
  return (target, propertyKey) => {
    const symParamTypes = Symbol.for("design:paramtypes");
    if (propertyKey === undefined) {
      target[symParamTypes] = types;
    }
    else {
      const symProperties = Symbol.for("design:properties");
      let properties, property;
      if (Object.prototype.hasOwnProperty.call(target, symProperties)) {
        properties = target[symProperties];
      }
      else {
        properties = target[symProperties] = {};
      }
      if (Object.prototype.hasOwnProperty.call(properties, propertyKey)) {
        property = properties[propertyKey];
      }
      else {
        property = properties[propertyKey] = {};
      }
      property[symParamTypes] = types;
    }
  };
}

Notes

  • Though it may seem counterintuitive, the methods on Reflect place the parameters for the metadata key and metadata value before the target or property key. This is due to the fact that the property key is the only optional parameter in the argument list. This also makes the methods easier to curry with Function#bind. This also helps reduce the overall footprint and complexity of a metadata-producing decorator that could target both a class or a property:
function ParamTypes(...types) {
  // as propertyKey is effectively optional, its easier to use here
  return (target, propertyKey) => { Reflect.defineMetadata("design:paramtypes", types, target, propertyKey); }

  // vs. having multiple overloads with the target and key in the front:
  //
  // return (target, propertyKey) => {
  //    if (propertyKey === undefined) {
  //      Reflect.defineMetadata(target, "design:paramtypes", types);
  //    }
  //    else {
  //      Reflect.defineMetadata(target, propertyKey, "design:paramtypes", types);
  //    }
  // }
  //
  // vs. having a different methods for the class or a property:
  //
  // return (target, propertyKey) => {
  //    if (propertyKey === undefined) {
  //      Reflect.defineMetadata(target, "design:paramtypes", types);
  //    }
  //    else {
  //      Reflect.definePropertyMetadata(target, propertyKey, "design:paramtypes", types);
  //    }
  // }
}
  • To enable experimental support for metadata decorators in your TypeScript project, you must add "experimentalDecorators": true to your tsconfig.json file.
  • To enable experimental support for auto-generated type metadata in your TypeScript project, you must add "emitDecoratorMetadata": true to your tsconfig.json file.
    • Please note that auto-generated type metadata may have issues with circular or forward references for types.

Issues

  • A poorly written mutating decorator for a class constructor could cause metadata to become lost if the prototype chain is not maintained. Though, not maintaining the prototype chain in a mutating decorator for a class constructor would have other negative side effects as well. @rbuckton
    • This is mitigated if the mutating decorator returns a class expression that extends from the target, or returns a proxy for the decorator. @rbuckton
  • Metadata for a method is attached to the class (or prototype) via the property key. It would not then be available if trying to read metadata on the function of the method (e.g. "tearing-off" the method from the class). @rbuckton