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 🙏

© 2024 – Pkg Stats / Ryan Hefner

json-to-dto

v0.1.2

Published

A TypeScript library designed for efficiently transforming JSON objects into predefined classes annotated with `@Class` and `@Prop` decorators.

Downloads

11

Readme

json-to-dto

Build Status Coverage Status

json-to-dto is a TypeScript library designed for efficiently transforming JSON objects into predefined classes annotated with @Class and @Prop decorators.

During the conversion, it can discard properties not marked with @Prop, perform type conversion and validation on the marked properties, and set default values for missing properties.

Why json-to-dto?

In game development, business logic often involves processing large amounts of interconnected player data and configuration data in memory. Player data from the database needs to be combined for transmission to the client. Configuration data from CMS and Excel spreadsheets must be validated, combined, with sensitive fields removed before sending to the client.

Unlike the class-transformer library, which relies on reflection and can be slower due to the overhead of metadata processing, json-to-dto uses a straightforward approach to convert JSON into class instances, saving CPU resources and reducing server costs.

Directly defining methods on DTO classes (similar to the Active Record pattern) simplifies writing business logic. Sharing DTO model code with the client makes backend data models usable on the frontend out of the box, saving significant development time.

Installation

npm install json-to-dto

Usage

Simple Conversion

To convert a simple JSON object into a class instance:

import { convertToType, Class, Prop, PrimitiveType } from 'json-to-dto';

const json = {
  id: 1,
  name: 'Tifa Lockhart',
  age: 20,
  height: 1.67,
  weight: '52.16',
  birthday: '1987-05-03T07:00:00.000Z',
  skills: ['hand-to-hand combat', 'martial arts', 'limit break'],
  online: true,
};

@Class()
class Character {
  @Prop({ type: PrimitiveType.Int })
  id: number;

  @Prop({ type: PrimitiveType.String })
  name: string;

  @Prop({ type: PrimitiveType.Int })
  age: number;

  @Prop({ type: PrimitiveType.Float })
  height: number;

  // weight will be automatically converted to number
  @Prop({ type: PrimitiveType.Float })
  weight: number;

  @Prop({ type: Date })
  birthday: Date;

  @Prop({ type: [PrimitiveType.String] })
  skills: string[];

  @Prop({ type: PrimitiveType.Boolean })
  online: boolean;
}

const character = convertToType(json, Character);
console.log(character);
// Output:
// Character {
//   id: 1,
//   name: 'Tifa Lockhart',
//   age: 20,
//   height: 1.67,
//   weight: 52.16,
//   birthday: 1987-05-03T07:00:00.000Z,
//   skills: [ 'hand-to-hand combat', 'martial arts', 'limit break' ],
//   online: true
// }

Default and Optional Value

When convert JSON objects into Address instances, the street property will default to an empty string if not provided, and the optional zip property will not be included in the converted instance if undefined in the JSON object.

import { convertToType, Class, Prop, PrimitiveType } from 'json-to-dto';

const json = [
  {
    street: '7th Heaven',
    city: 'Midgar',
    zip: '12345',
  },
  {
    city: 'Nibelheim',
  },
];

@Class()
class Address {
  @Prop({ type: PrimitiveType.String, default: '' })
  street: string;

  @Prop({ type: PrimitiveType.String })
  city: string;

  @Prop({ type: PrimitiveType.String, isOptional: true })
  zip?: string;
}

const addresses = convertToType(json, [Address]);
console.log(addresses);
// Output:
// [
//   Address { street: '7th Heaven', city: 'Midgar', zip: '12345' },
//   Address { street: '', city: 'Nibelheim' }
// ]

Nested Objects and Arrays

Handling nested objects and arrays is straightforward. json-to-dto will recursively deserialize nested structures:

import { convertToType, Class, Prop, PrimitiveType } from 'json-to-dto';

const json = {
  id: 1,
  name: 'Tifa Lockhart',
  // ... other fields ...
  friends: [
    {
      id: 2,
      name: 'Cloud Strife',
      relationship: 2,
      friends: [
        {
          id: 1,
          name: 'Tifa Lockhart',
          relationship: 2,
        },
        // ... other friends ...
      ],
    },
  ],
  // ... other fields ...
};

enum Relationship {
  TEAMMATE = 1,
  LOVER = 2,
}

@Class()
class Friend {
  @Prop({ type: PrimitiveType.Int })
  id: number;

  @Prop({ type: PrimitiveType.String })
  name: string;

  @Prop({ type: PrimitiveType.Int })
  relationship: Relationship;

  @Prop({ type: [Friend], default: [] })
  friends: Friend[];
}

@Class()
class Character {
  @Prop({ type: PrimitiveType.Int })
  id: number;

  @Prop({ type: PrimitiveType.String })
  name: string;

  @Prop({ type: [Friend] })
  friends: Friend[];
}

const character = convertToType(json, Character);
console.log(character.friends[0].name);
// Output: Cloud Strife

Adding Methods

You can define methods on your DTO classes to include business logic directly within your models:

const json = {
  id: 2,
  name: 'Cloud Strife',
  relationship: 2,
  friends: [
    { id: 1, name: 'Tifa Lockhart', relationship: 2 },
    // ... other friends ...
  ],
};

enum Relationship {
  TEAMMATE = 1,
  LOVER = 2,
}

@Class()
class Friend {
  @Prop({ type: PrimitiveType.Int })
  id: number;

  @Prop({ type: PrimitiveType.String })
  name: string;

  @Prop({ type: PrimitiveType.Int })
  relationship: Relationship;

  @Prop({ type: [Friend], default: [] })
  friends: Friend[];

  isLover(name: string): boolean {
    return (
      name !== this.name && this.friends.findIndex((f) => f.name === name && f.relationship === Relationship.LOVER) > -1
    );
  }
}

const friend = convertToType(json, Friend);
console.log(friend.isLover('Tifa Lockhart'));
// Output: true

Custom Validation

The custom validation function should follow this signature:

/**
 * PropValidator is the function signature for custom validation of a property value of an object.
 * @param value - the value of the property
 * @param name - the name of the property in the object
 * @param obj - the object containing the property
 * @returns - return undefined when validation passes; return a string of the failure reason when validation fails.
 */
export type PropValidator = (val: any, name: string, obj: Record<string, any>) => string | undefined;

Example of using custom PropValidator and built-in PropValidator

import {
  convertToType,
  Class,
  Prop,
  PrimitiveType,
  NewValidatorMin,
  NewValidatorMax,
  NewValidatorArrayNotEmpty,
  NewValidatorStringNotEmpty,
} from 'json-to-dto';

const json = {
  id: 1,
  name: 'Tifa Lockhart',
  skills: ['hand-to-hand combat', 'martial arts', 'limit break'],
  birthday: '1987-05-03T07:00:00.000Z',
  registered: 547023500000,
};

@Class()
class Character {
  @Prop({ type: PrimitiveType.Int, validate: [NewValidatorMin(1), NewValidatorMax(9999)] })
  id: number;

  @Prop({ type: PrimitiveType.String, validate: NewValidatorStringNotEmpty() })
  name: string;

  @Prop({ type: [PrimitiveType.String], validate: NewValidatorArrayNotEmpty() })
  skills: string[];

  @Prop({ type: Date })
  birthday: Date;

  /** Custom validator: registered must be later than birthday */
  @Prop({
    type: Date,
    validate: (val, name, obj) =>
      val.getTime() >= obj['birthday'].getTime() ? undefined : 'registered must be later than birthday',
  })
  registered: Date;
}

try {
  const character = convertToType(json, Character);
} catch (err) {
  console.error('' + err);
  // Output:
  // Error: failed to convert val([object Object]) to Class(Character), invalid property(registered:Sat May 02 1987 23:58:20 GMT-0700 (Pacific Daylight Time)), validation error(registered must be later than birthday)
}

Inheritance and Polymorphism

Define classes with inheritance to model complex data structures. Here’s how you can use inheritance with json-to-dto:

import { convertToType, Class, Prop, PrimitiveType } from 'json-to-dto';

const json = {
  weapon: {
    name: 'Leather Gloves',
    attack: 10,
  },
  armor: {
    name: 'Leather Vest',
    defense: 10,
  },
  accessory: {
    name: 'Protective Boots',
    defense: 5,
    effect: 'Prevents poison',
  },
};

@Class()
class BaseEquipment {
  @Prop({ type: PrimitiveType.String })
  name: string;

  @Prop({ type: PrimitiveType.String })
  type: string;
}

@Class()
class OffensiveEquipment extends BaseEquipment {
  @Prop({ type: PrimitiveType.Int })
  attack: number;
}

@Class()
class DefensiveEquipment extends BaseEquipment {
  @Prop({ type: PrimitiveType.Int })
  defense: number;
}

@Class()
class Weapon extends OffensiveEquipment {
  @Prop({ type: PrimitiveType.String, default: 'Weapon' })
  type: string;
}

@Class()
class Armor extends DefensiveEquipment {
  @Prop({ type: PrimitiveType.String, default: 'Armor' })
  type: string;
}

@Class()
class Accessory extends DefensiveEquipment {
  @Prop({ type: PrimitiveType.String, default: 'Accessory' })
  type: string;

  @Prop({ type: PrimitiveType.String })
  effect: string;
}

@Class()
class Equipment {
  @Prop({ type: Weapon })
  weapon: Weapon;

  @Prop({ type: Armor })
  armor: Armor;

  @Prop({ type: Accessory })
  accessory: Accessory;
}

const equipment = convertToType(json, Equipment);
console.log(equipment);
// Output:
// Equipment {
//   weapon: Weapon { type: 'Weapon', attack: 10, name: 'Leather Gloves' },
//   armor: Armor { type: 'Armor', defense: 10, name: 'Leather Vest' },
//   accessory: Accessory {
//     type: 'Accessory',
//     effect: 'Prevents poison',
//     defense: 5,
//     name: 'Protective Boots'
//   }
// }

Explore More Techniques

Please refer to the src/tests/convert-to-type.dto.ts file for more advanced usage and techniques.