npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

typescript-object-serializer

v2.0.1

Published

Powerful Scalable Typescript object serializer

Readme

typescript-object-serializer

Typescript library to convert javascript object to typescript class and vice versa

CHANGELOG Useful snippets Migration Guide

Installation and configuration

> npm install typescript-object-serializer

Required configure tsconfig.json:

{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

And it is ready to use! If necessary enable auto-detection types of serializable properties: - required additional configuration:

  1. Install reflect-metadata dependency:
> npm install reflect-metadata
  1. Configure tsconfig.json:
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}
  1. Import reflect-metadata in polyfills.ts or in top of index.ts file
import 'reflect-metadata';

Usage

Basic usage

import {
  deserialize,
  serialize,
  property,
  SnakeCaseExtractor,
} from 'typescript-object-serializer';

class Person {

  @property()
  public declare name: string;

  @property(SnakeCaseExtractor)
  public declare lastName: string;

}

const person = deserialize(Person, {
  name: 'John',
  last_name: 'Doe',
});

console.log(person instanceof Person); // true
console.log(person.name); // "John"
console.log(person.lastName); // "Doe"
console.log(serialize(person)) // { name: "John", last_name: "Doe" }

Deep serializable property

import {
  deserialize,
  property,
  SnakeCaseExtractor,
} from 'typescript-object-serializer';

class Person {

  @property()
  public declare name: string;

  @property(SnakeCaseExtractor)
  public declare lastName: string;

}

class Employee {

  @property()
  public declare id: number;
    
  @property()
  @propertyType(Person)
  public declare person: Person;

}

const employee = deserialize(Employee, {
  id: 1,
  person: {
    name: 'John',
    last_name: 'Doe',
  },
});

console.log(employee.person); // Person { name: "John", lastName: "Doe" }

Extend serializable class

import {
  deserialize,
  property,
} from 'typescript-object-serializer';

class Person {
  @property()
  public declare name: string;
}

class Employee extends Person {
  @property()
  public declare id: number;
}

const employee = deserialize(Employee, {
  id: 1,
  name: 'John',
});

console.log(employee); // Employee { name: "John", id: 1 }

Auto-detect property types

import {
  deserialize,
  property,
} from 'typescript-object-serializer';

class Person {
  @property()
  public declare name: string;
}

class Employee {
  @property()
  public declare id: number;

  @property()
  public declare person: Person; // <- Type will be extracted from property metadata
}

const employee = deserialize(Employee, {
  id: 1,
  person: {
    name: 'John',
  },
});

console.log(employee); // Employee { id: 1, person: Person { name: 'John' } }

Handle arrays of data

import {
  deserialize,
  property,
  SnakeCaseExtractor,
  propertyType,
} from 'typescript-object-serializer';

class Person {

  @property()
  public declare name: string;

  @property(SnakeCaseExtractor)
  public declare lastName: string;

}

class Employee {

  @property()
  public declare id: number;

  @property()
  @propertyType(Person)
  public declare person: Person;

}

class Department {

  @property()
  public declare title: string;

  @property()
  @propertyType(Employee)
  public declare employees: Employee[];

}

const employees = [
  {
    id: 1,
    person: {
      name: 'John',
      last_name: 'Doe',
    },
  },
  {
    id: 2,
    person: {
      name: 'Jane',
      last_name: 'Doe',
    },
  },
].map(e => deserialize(Employee, e));

console.log(employees.length); // 2
console.log(employees[0]); // Employee { id: 1, person: Person { name: "John", lastName: "Doe" } }

const department = deserialize(Department, {
  title: 'Department title',
  employees: [
    {
      id: 1,
      person: {
        name: 'John',
        last_name: 'Doe',
      },
    },
    {
      id: 2,
      person: {
        name: 'Jane',
        last_name: 'Doe',
      },
    },
  ],
});

console.dir(department, { depth: 3 }); // Department { title: "Department title", employees [ Employee { id: 1, person: Person { name: "John", lastName: "Doe" } }, Employee { id: 2, person: Person { name: "Jane", lastName: "Doe" } } ] }
console.dir(serialize(department), { depth: 3 }); // {title: "Department title", employees: [{id: 1, person: {name: "John", last_name: "Doe"}}, {id: 2, person: {name: "Jane", last_name: "Doe"}}]}

Property extractor

StraightExtractor [Default]

Extracts property with same name

import {
  deserialize,
  property,
  StraightExtractor,
} from 'typescript-object-serializer';

class Person {

  @property()
  public declare name: string;

  @property(StraightExtractor) // Same as @property()
  public declare lastName: string;

}

const person = deserialize(Person, {
  name: 'John',
  lastName: 'Doe',
});

console.log(person); // Person { name: "John", lastName: "Doe" }

SnakeCaseExtractor

Extracts property by name transformed from camelCase to snake_case

import {
  deserialize,
  property,
  SnakeCaseExtractor,
} from 'typescript-object-serializer';

class Person {

  @property()
  public declare name: string;

  @property(SnakeCaseExtractor)
  public declare lastName: string;

}

const person = deserialize(Person, {
  name: 'John',
  last_name: 'Doe',
});

console.log(person); // Person { name: "John", lastName: "Doe" }

OverrideNameExtractor

Extracts property by name passed to use static method

import {
  deserialize,
  property,
  OverrideNameExtractor,
} from 'typescript-object-serializer';

class Department {

  @property(OverrideNameExtractor.use('department_id'))
  public declare id: string;

}

const department = deserialize(Department, {
  department_id: '123',
});

console.log(department); // Department { id: "123" }

Property type

Declares type for property. Required if not possible to detect type from property declaration (for example array of data)

Property type basic

import {
  property,
  propertyType,
} from 'typescript-object-serializer';

class Person {

  @property()
  public declare name: string;

  @property(SnakeCaseExtractor)
  public declare lastName: string;

}

class Employee {

  @property()
  declare id: number;

  @property()
  @propertyType(Person) // <- Not required if auto-detection types enabled
  public declare person: Person;

}

class Department {

  @property()
  @propertyType(Employee) // <- Required because not possible to detect type from property declaration (property metadata seems like Array)
  public declare employees: Employee[];

}

Conditional property type

import {
  deserialize,
  property,
  propertyType,
} from 'typescript-object-serializer';

class SuccessResult {
  @property()
  public declare data: Record<string, unknown>;
}
class FailedResult {
  @property()
  public declare error: string;
}
class UnmatchedResult {

}

class Results {

  @property()
  @propertyType(SuccessResult, (value: any) => value?.state === 'SUCCESS')
  @propertyType(FailedResult, (value: any) => value?.state === 'FAIL')
  @propertyType(UnmatchedResult) // <- Default if no one other matched
  public declare results: Array<SuccessResult | FailedResult | UnmatchedResult>;

}

const results = deserialize(Results, {
  results: [
    {
      state: 'SUCCESS',
      data: {
        some_data: 'data',
      },
    },
    {
      state: 'UNKNOWN',
    },
    {
      state: 'FAIL',
      error: 'result error',
    },
  ],
});

console.log(results.results[0]); // SuccessResult { data: { some_data: "data" } }
console.log(results.results[1]); // UnmatchedResult {  }
console.log(results.results[2]); // FailedResult { error: "result error" }

// For strict type check (fewer possible runtime errors)
class ResultsWithStrictTypeCheck {

  @property()
  @propertyType(SuccessResult, (value: unknown) => typeof value === 'object'
    && value !== null
    && 'state' in value
    && value.state === 'SUCCESS',
  )
  @propertyType(FailedResult, (value: unknown) => typeof value === 'object'
    && value !== null
    && 'state' in value
    && value.state === 'FAIL',
  )
  @propertyType(UnmatchedResult)
  public declare results: Array<SuccessResult | FailedResult | UnmatchedResult>;

}

Create serializable object

import {
  create,
  property,
} from 'typescript-object-serializer';

class Person {

  @property()
  public declare lastName: string;

  @property()
  public declare firstName: string;

}

// Recommended instead of createPartial()
const person = create(Person, {
  firstName: 'John',
  lastName: 'Doe',
});
console.log(person); // Person { firstName: "John", lastName: "Doe" }

const partialPerson = createPartial(Person);
console.log(partialPerson); // Person { }

Clone serializable object

import {
  create,
  clone,
  property,
} from 'typescript-object-serializer';

class Person {

  @property()
  public declare lastName: string;

  @property()
  public declare firstName: string;

}

const person = create(Person, {
  firstName: 'John',
  lastName: 'Doe',
});

const personClone = clone(person);

console.log(personClone); // Person { firstName: "John", lastName: "Doe" }
console.log(person === personClone); // false

Serialize serializable object

Serialize object and all nested serializable objects to simple javascript object

import {
  create,
  serialize,
  property,
  SnakeCaseExtractor,
} from 'typescript-object-serializer';

class Person {

  @property(SnakeCaseExtractor)
  public declare lastName: string;

  @property(SnakeCaseExtractor)
  public declare firstName: string;

}

const person = create(Person, {
  firstName: 'John',
  lastName: 'Doe',
});

console.log(serialize(person)); // { first_name: "John", last_name: "Doe" }

Modify property value

In case

  1. Property value has type mismatch (string or null when expected number)
import {
  deserialize,
  modifier,
  Modifier,
  property,
  serialize,
} from 'typescript-object-serializer';

class StringAgeModifier extends Modifier {
  public override onSerialize(data: number): string {
    return String(data);
  }
  public override onDeserialize(data: string): number {
    return Number(data);
  }
}

class Person {

  @property()
  @modifier(StringAgeModifier)
  public declare age: number;

}

const person = deserialize(Person, {
  age: '25',
});

console.log(person); // Person { age: 25 }
console.log(typeof person.age); // number;
console.log(serialize(person)); // { age: "25" }
  1. Modify property value format
import {
  serialize,
  deserialize,
  property,
  StraightExtractor,
} from 'typescript-object-serializer';

// Like if database required full string date format
class BirthDateModifier extends Modifier {
  public override onSerialize(value: string): string {
    return new Date(value).toISOString();
  }
}

class Person {

  @property()
  @modifier(BirthDateModifier)
  public declare birthDate: string;

}

const person = create(Person, {
  birthDate: '2000-05-06',
});


console.log(person); // Person { birthDate: "2000-05-06"}
console.log(serialize(person)); // { birthDate: "2000-05-06T00:00:00.000Z" }
  1. Modification also handful to serialize non-json values like Date, or custom non-serializable classes

Advanced usage

Custom extractor

It is possible to develop your own extractor according to your needs Example 1: PrivateSnakeCaseExtractor. Extracts snake_case property to camelCase property with leading _

import {
  deserialize,
  property,
  SnakeCaseExtractor,
} from 'typescript-object-serializer';

/* Extract value from `snake_case` property to "private" `camelCase` property  */
class PrivateSnakeCaseExtractor extends SnakeCaseExtractor {
  constructor(
    key: string,
    modifier?: Modifier,
  ) {
    super(
      key.replace(/^_/, ''),
      modifier,
    );
  }
}

class Department {

  @property(PrivateSnakeCaseExtractor)
  private declare _departmentId: string;

}

const department = deserialize(Department, {
  department_id: '123',
});

console.log(department); // Department { _departmentId: "123" }

Example 2: DeepExtractor. Extracts value from deep object

import {
  deserialize,
  serialize,
  property,
  Extractor,
  ExtractionResult,
} from 'typescript-object-serializer';

 /* Extract value from deep object (transform to plane object) */
class DeepExtractor extends Extractor {

  public static byPath<C extends typeof DeepExtractor>(path: string): C {
    return class extends DeepExtractor {
      constructor(_: string, modifier?: Modifier) {
        super(path, modifier);
      }
    } as any;
  }

  private static getObjectByPath(dataObject: any, keys: string[]): any {
    let extracted: any = dataObject;
    keys.forEach(key => {
      if (!extracted) {
        return undefined;
      }
      extracted = (extracted as any)[key];
    });
    return extracted;
  }

  private static getOrCreateObjectByPath(dataObject: any, keys: string[]): any {
    let currentObject = dataObject;
    keys.forEach(key => {
      if (!currentObject.hasOwnProperty(key)) {
        currentObject[key] = {};
      }
      currentObject = currentObject[key];
    });
    return currentObject;
  }

  constructor(
    key: string,
    modifier?: Modifier,
  ) {
    super(key, modifier);
  }

  public extract(data: any): ExtractionResult {
    if (typeof data !== 'object' || data === null) {
      return {
        data: undefined,
        path: this.key,
      };
    }
    return {
      data: this.modifier.onDeserialize(
        DeepExtractor.getObjectByPath(data, this.key.split('.')),
      ),
      path: this.key,
    };
  }

  public apply(applyObject: any, value: unknown): void {
    const keys = this.key.split('.');
    const dataObject = DeepExtractor.getOrCreateObjectByPath(applyObject, keys.slice(0, -1));
    dataObject[keys[keys.length - 1]] = this.modifier.onSerialize(value);
  }

}

class AgeModifier extends Modifier {
  public override onDeserialize(value: string): number {
    return Number(value);
  }
  public override onSerialize(value: number): string {
    return String(value);
  }
}

class Person {

  @property()
  public declare id: number;

  @property(DeepExtractor.byPath('data.person.age'))
  @modifier(AgeModifier)
  public declare age: number;

  @property(DeepExtractor.byPath('data.person.last_name'))
  public declare lastName: string;

  @property(DeepExtractor.byPath('data.person.first_name'))
  public declare firstName: string;

}

const person = deserialize(Person, {
  id: 123,
  data: {
    person: {
      age: '25',
      last_name: 'John',
      first_name: 'Doe',
    },
  },
});

console.log(person); // Person { lastName: "John", id: 123, age: 25, firstName: "Doe" }

console.log(serialize(person)); // { id : 123, data: { person: {age: "25", last_name: "John", first_name: "Doe" } } }

Only deserializable property

Example 1: Using custom extractor

import {
  deserialize,
  serialize,
  property,
  StraightExtractor,
} from 'typescript-object-serializer';

class OnlyDeserializeStraightExtractor extends StraightExtractor {
  public apply(applyObject: any, value: unknown): void {
  }
}

class Department {
  @property(OnlyDeserializeStraightExtractor)
  public declare id: number;

  @property()
  public declare title: string;
}

const department = deserialize(Department, {
  id: 123,
  title: 'Department title',
});
console.log(department); // Department { id: 123, title: "Department title" }

console.log(serialize(department)); // { title: "Department title" }

Example 2: Using modificator (Recommended)

import {
  deserialize,
  modifier,
  Modifier,
  serialize,
  property,
} from 'typescript-object-serializer';

class OnlyDeserializableModifier extends Modifier {
  public override onSerialize(value: unknown): undefined {
    return undefined;
  }
  public override onDeserialize(data: unknown): unknown {
    return data;
  }
}

class Department {
  @property()
  @modifier(OnlyDeserializableModifier)
  public declare id: number;

  @property()
  public declare title: string;
}

const department = deserialize(Department, {
  id: 123,
  title: 'Department title',
});
console.log(department); // Department { id: 123, title: "Department title" }

console.log(serialize(department)); // { title: "Department title" }

Getters and setters

It is possible to serialize getter and deserialize setter property

import {
  deserialize,
  serialize,
  property,
} from 'typescript-object-serializer';

class PersonWithGetter {
  constructor(
    public firstName: string,
    public lastName: string,
  ) {
}

  @property()
  public get fullName(): string {
    return this.firstName + ' ' + this.lastName;
  }
}

const personWithGetter = new PersonWithGetter('John', 'Doe');
console.log(serialize(personWithGetter)); // { fullName: "John Doe" }

class PersonWithSetter {
  public declare firstName: string;
  public declare lastName: string;

  @property()
  public set fullName(value: string) {
    const [firstName, lastName] = value.split(' ');
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

const deserialized = deserialize(PersonWithSetter, {
  fullName: 'John Doe',
});

console.log(deserialized); // PersonWithSetter { firstName: "John", lastName: "Doe" }

Syntactic sugar: SerializableObject

Class SerializebleObject for easy access to serializer methods like serialize, deserialize, create, clone, deserializeArray. it makes possible to import 'typescript-object-serializer' only at class declaration file but not to import it where serialization/deserialization used.

import {
  SerializableObject,
  property,
} from 'typescript-object-serializer';

class Item extends SerializableObject {
  @property()
  public id: number;
  @property()
  public title: string;
}

const items = Item.deserializeArray([
  {
    id: 1,
    title: 'First item',
  },
  {
    id: 2,
    title: 'Second item',
  },
]);
console.log(items); // [ Item { id: 1, title: "First item" }, Item { id: 2, title: "Second item" } ]

const firstItem = items[0];
const firstItemClone = firstItem.clone();
console.log(firstItemClone); // Item { id: 1, title: "First item" }
console.log(firstItemClone === firstItem); // false
console.log(firstItemClone.serialize()); // { id: 1, title: 'First item' }

const newItem = Item.create({
  id: 3,
  title: 'New item',
});
console.log(newItem); // Item { id: 3, title: "New item" }

Data validation

It is possible to validate data before deserialization. Method validate from module typescript-object-serializer/validators returns Array of validation errors. It returns empty array if data is valid.

Basic validation

import { property } from 'typescript-object-serializer';
import {
  propertyValidators,
  RequiredValidator,
  StringLengthValidator,
  validate,
} from 'typescript-object-serializer/validators';

class Person {
  @property()
  @propertyValidators([RequiredValidator, StringLengthValidator.with({ min: 1 })])
  public name: string;
}

const resultRequired = validate(Person, {});
console.log(resultRequired); // [ ValidationError { message: "Property is required", path: "name" } ]

const resultEmpty = validate(Person, {
  name: '',
});
console.log(resultEmpty); // [ ValidationError { message: "Property length should be greater than or equal 1", path: "name" } ]

Deep validation

import {
  property,
  propertyType,
  SnakeCaseExtractor,
} from 'typescript-object-serializer';
import {
  propertyValidators,
  RequiredValidator,
  validate,
} from 'typescript-object-serializer/validators';

class Address {
  @property()
  @propertyValidators([RequiredValidator])
  public declare city: string;
}

class Employee {
  @property()
  @propertyValidators([RequiredValidator])
  public declare name: string;

  @property()
  @propertyValidators([RequiredValidator])
  @propertyType(Address)
  public declare address: Address;
}

class Department {
  @property(OverrideNameExtractor.use('department_employees'))
  @propertyType(Employee)
  public declare employees: Employee[];
}

class Organization {

  @property()
  @propertyType(Department)
  public declare departments: Department[];

}

const data = {
  departments: [
    {
      department_employees: [
        {
          name: 'John Doe',
          address: {
            city: 'New York',
          },
        },
        {
          address: {
            city: 'London',
          },
        },
      ],
    },
    {
      department_employees: [
        {
          name: 'Jane Doe',
          address: {
          },
        },
        {
          name: 'Jane Smith',
          address: {
            city: 'Berlin',
          },
        },
      ],
    },
  ],
};

const validationResult = validate(Organization, data);

console.log(validationResult); // [ ValidationError { message: 'Property is required', path: "departments.[0].department_employees.[1].name" }, ValidationError { message: "Property is required", path: "departments.[1].department_employees.[0].address.city" } ]

Custom validator

import { property } from 'typescript-object-serializer';
import {
  propertyValidators,
  validate,
  ValidationError,
  Validator,
} from 'typescript-object-serializer/validators';

class VINValidator extends Validator {
  public validate(value: unknown, path: string): ValidationError | undefined {
    if (typeof value !== 'string') {
      return;
    }
    if (!/^[A-HJ-NPR-Z0-9]{17}$/i.test(value)) {
      return new ValidationError('Invalid VIN', path);
    }
  }
}

class Vehicle {
  @property()
  @propertyValidators([VINValidator])
  public declare vin: string;
}

const validationResult = validate(Vehicle, { vin: '345435' });

console.log(validationResult); // [ ValidationError { message: "Invalid VIN", path: "vin" } ]

Custom validation error

It is possible to implement own validation errors at custom validators. It allows to

  1. Add some logic on validation result: filter critical and non-critical errors
  2. Add error class with predefined message (no need to write error message at validate() method)
import { property } from 'typescript-object-serializer';
import {
  propertyValidators,
  validate,
  ValidationError,
  Validator,
} from 'typescript-object-serializer/validators';

class PasswordCriticalValidationError extends ValidationError {
  constructor(
    path: string,
  ) {
    super('Password too short', path);
  }
}

class PasswordWarnValidationError extends ValidationError {
  constructor(
    path: string,
  ) {
    super('Password is weak', path);
  }
}

class PasswordValidator extends Validator {

  public validate(value: unknown, path: string): ValidationError | undefined {
    if (typeof value !== 'string') {
      return;
    }

    if (value.length < 4) {
      return new PasswordCriticalValidationError(path);
    }

    if (value.length < 6) {
      return new PasswordWarnValidationError(path);
    }
  }
}

class LoginCredentials {
  @property()
  @propertyValidators([PasswordValidator])
  public declare password: string;
}

const shortPasswordResult = validate(LoginCredentials, { password: '123' });
console.log(shortPasswordResult); // [ PasswordCriticalValidationError { message: "Password too short", path: "password" } ]

const weakPasswordResult = validate(LoginCredentials, { password: '12345' });
console.log(weakPasswordResult); // [ PasswordWarnValidationError { message: "Password is weak", path: "password" } ]

const criticalErrors = weakPasswordResult.filter(error => !(error instanceof PasswordWarnValidationError));
console.log(criticalErrors); // []