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

jsontoclass

v2.3.1

Published

JSON to class mapper

Downloads

5

Readme

Introduction

JTC is a library that serves three major purposes:

  • Provides strong defence from invalid JSON data
  • Informs about any repairs in JSON after validation
  • Maps JSON objects into class instances

Getting Started

Create own class definition, then use it to create JTC instance. Keep in mind that all fields inside sample objects should be initialized by some meaningfull value, which will be used as default in case of trouble (null / undefined are not accepted).

All examples are written in TypeScript.

Complicated example right from start, see other paragraphs for more details.

import { JTC } from 'jsontoсlass';

class User {
   public name = '';
   public age = 0;
   public phone = new Phone();
   public notes: Note[] = [];
   public remarks: string[] = [];
   @JTC.map public drinked: Drink = {};
   @JTC.date('ms') public birthday = new Date(0);

   sayHello() {
      return `Hello, my name is ${this.name}`;
   }
}


class SuperUser extends User {
   public superPower: string = ``;
   @JTC.null public someField = 0;
}


class Admin extends SuperUser {
   public motto = ``
}

class Phone {
   public country = '';
   public number = 0;
   public code = 0;

   getFullNumber() {
      return `+${this.code} ${this.number}`;
   }
}


class Note {
   public text = '';
   public timestamp = 0;
   public isDone = false;

   getSatus() {
      return `I planned ${this.text} on ${new Date(this.timestamp).toLocaleString()} and this is ${this.isDone ? '' : 'not yet '} done`;
   }
}


interface Drink {
   [drink: string]: number;
}


const usersJTC = new JTC('Tester', () => {
   const user = new User();
   const superUser = new SuperUser();
   const admin = new Admin();
   const samples: [User, ...User[]] = [user, superUser, admin];

   for (const sample of samples) {
      sample.notes = [new Note()];
      sample.remarks = [''];
      sample.drinked = {
         'drink': 0,
      };
   }
  
   return samples;
});



function getUser() {
   return {
      name: 0,
      age: '21',
      phone: null,
      birthday: 1562000000000,
      notes: [
         { text: 'Go to shop', timestamp: 1552170070897 },
         null,
         { text: 'Feed the dog', timestamp: undefined, isDone: true },
         { junk: '......' },
         null
      ],
      remarks: [`What a beatful day`, 23, {}],
      drinked: {
         'coffe': { number: 4 },
         'beer': 8,
         'vine': '12'
      },
      ghost: 'booooo'
   };
}


function getSuperUser() {
   const user = getUser();

   user['superPower'] = 'Procrastination';
   user['someField'] = null;

   return user;
}


function getAdmin() {
   const superUser = getSuperUser();

   superUser['someField'] = 12;
   superUser['motto'] = `I am the LAW!`;

   return superUser;
}


const jsonUser = getUser() as any;
const jsonUsers = [ null, getSuperUser(), new User(), 1234, getUser(), null, getSuperUser(), `111!!11`, new User(), getAdmin(), getUser(), {} ] as any[];

const validatedUser = usersJTC.single(jsonUser).data;
console.log(`Single validation: `, { json: jsonUser, validated: validatedUser});

const validatedUsers = usersJTC.array(jsonUsers).data;
console.log(`Array validation: `, { json: jsonUsers, validated: validatedUsers });

Array

If your model contain field, which value is an array, you need to provide sample of what do you expect inside this array, you can do this inside sample function.

import { JTC } from 'jsontoсlass';

class User {
   public name = '';
   public age = 0;
   public notes: Note[] = [];
   public remarks: string[] = [];


   sayHello() {
      return `Hello, my name is ${this.name}`;
   }
}


class Note {
   public text = '';
   public timestamp = 0;
   public isDone = false;


   getSatus() {
      return `I planned ${this.text} on ${new Date(this.timestamp).toLocaleString()} and this is ${this.isDone ? '' : 'not yet '} done`;
   }
}


const usersJTC = new JTC('Tester', () => {
    const user = new User();
    user.notes = [ new Note() ];
    user.remarks = [''];
    return [ user ];
});


const income = {
   name: 'Grathan Muller',
   age: 21,
   notes: [
      { text: 'Go to shop',  timestamp: 1552170070897, isDone: false },
      { text: 'Feed the dog',  timestamp: 1552170020897, isDone: true },
   ],
   remarks: [`What a beatful day`]
} as any;

const user = usersJTC.single(income).data;

console.log(income, user)
console.log(user.sayHello());
console.log(user.remarks.join(', '));
user.notes.forEach(note => console.log(note.getSatus()));

Map

In order to let JTC understand that some object is a map you should use "JTC.map" decorator on class field, as for arrays, sample of inner value should be provided.

import { JTC } from 'jsontoсlass';

class User {
   public name = '';
   public age = 0;
   @JTC.map public drinked: Drink = {};

   sayHello() {
      return `Hello, my name is ${this.name}`;
   }
}

interface Drink {
   [drink: string]: number;
}

 const usersJTC = new JTC('Tester', () => {
    const user = new User();
    user.drinked = {
        'drink': 0,
    };
    return [ user ];
});

const income = {
   name: 'Grathan Muller',
   age: 21,
   drinked: {
      'coffe': 4,
      'beer': 8,
      'vine': 12
   },
} as any;

const user = usersJTC.single(income).data;

console.log(income, user);
console.log(user.sayHello());
console.log(user.phone.getFullNumber());
console.log(user.drinked);

Null

If you are expecting that value can be null, you can decorate class field with "JTC.null", in that case value will be left null as is.

import { JTC } from 'jsontoсlass';

class User {
   public name = '';
   public age = 0;
   @JTC.null public phone = new Phone();

   sayHello() {
      return `Hello, my name is ${this.name}`;
   }
}

class Phone {
   public country = '';
   public number = 0;
   public code = 0;
   
   getFullNumber() {
      return `+${this.code} ${this.number}`;
   }
}

 const usersJTC = new JTC('Tester', () => [ new User() ]);

const income = {
   name: 'Grathan Muller',
   age: 21,
   phone: null,
} as any;

const user = usersJTC.single(income).data;

console.log(income, user);
console.log(user.sayHello());
console.log(user.phone);

Date

If you're want instantly convert primitive inside your json into js Date you can use "JTC.date" decorator with specifyed primitive type: ''ms / sec / str"

import { JTC } from 'jsontoсlass';

class User {
   public name = '';
   public age = 0;
   @JTC.date('ms') public birthday = new Date(0);

   sayHello() {
      return `Hello, my name is ${this.name}`;
   }
}

const usersJTC = new JTC('Tester', () => [ new User() ]);

const json = {
   name: 'Grathan Muller',
   age: 21,
   birthday: 1562000000000
} as any;

const user = usersJTC.single(json).data;

console.log({ json, user });

Inheritance

You probably wondering "Why sample function should return an array?" - because JTC can determine inheritance inside your json on fields name basis. Meaning you can specify few objects in the sample array, with condition, that all of them have common ancestor. Then during conversion your json objects will be mapped to the appropriate instances.

import { JTC } from 'jsontoсlass';

class User {
   public name = '';
   public age = 0;

   sayHello() {
      return `Hello, my name is ${this.name}`;
   }
}

class SuperUser extends User {
   public isSuper = true;
}

const usersJTC = new JTC('Tester', () => [ new User(), new SuperUser() ]);

const income = [
   {
      name: 'Grathan Muller',
      age: 21,
   }, 
   {
      name: 'Steven Gatz',
      age: 25,
      isSuper: true
   }
] as any;

const users = usersJTC.array(income).data;

console.log(income, users)

Unexpected field type

In case when inside your json some fields doesn't math data type that you expect:

  • Primitive - will be replaced by default value, provided in the sample;
  • Object - will be replaced by new instance of class, provided in the sample;
  • Array - will be replaced with empty array;
  • Map - will be replaced with empty object;
  • If corrupted value stored inside iterable, like map or array, it will be excluded from it;
  • If object doesn't have all of the required fields - they will be added with default values from sample;
  • If object have unexpected fields - they will be deleted;
import { JTC } from 'jsontoсlass';

class User {
   public name = '';
   public age = 0;
   public phone = new Phone();
   public notes: Note[] = [];
   public remarks: string[] = [];
   @JTC.map public drinked: Drink = {};

   sayHello() {
      return `Hello, my name is ${this.name}`;
   }
}

class Phone {
   public country = '';
   public number = 0;
   public code = 0;
   
   getFullNumber() {
      return `+${this.code} ${this.number}`;
   }
}

class Note {
   public text = '';
   public timestamp = 0;
   public isDone = false;

   getSatus() {
      return `I planned ${this.text} on ${new Date(this.timestamp).toLocaleString()} and this is ${this.isDone ? '' : 'not yet '} done`;
   }
}

interface Drink {
   [drink: string]: number;
}

const usersJTC = new JTC('Tester', () => {
      const user = new User();
      user.notes = [ new Note() ];
      user.remarks = [''];
      user.drinked = {
         'drink': 0,
      };
      return [ user ];
});

const income = {
   name: 0,
   age: '21',
   phone: null,
   notes: [
      { text: 'Go to shop',  timestamp: 1552170070897 },
      null, 
      { text: 'Feed the dog',  timestamp: undefined, isDone: true },
      { junk: '......' },
      null
   ],
   remarks: [`What a beatful day`, 23, {  }],
   drinked: {
      'coffe': { number: 4 },
      'beer': 8,
      'vine': '12'
   },
   ghost: 'booooo'
} as any;

const user = usersJTC.single(income).data;

console.log(income, user);
console.log(user.sayHello());
console.log(user.phone.getFullNumber());
user.notes.forEach(note => console.log(note.getSatus()));
console.log(user.remarks.join(', '));
console.log(user.drinked)

Debug Log

In situations when any repairs been done to your json you will see debug log in the console. Result of "single" / "array" functions provide some useful additional fields like:

  • isValid - boolean, will be true only if there were no fields that needed repairs
  • timestamt - Date, date when mapping been called
  • repairTree - object, based on which you see debug log in the console. Can be stored as json and viewed with "JTC.logRepairs" function later.

Errors

000

Samples function should return array with at least one value!

Invalid

const usersJTC = new JTC('Tester', () => []);

Valid

const usersJTC = new JTC('Tester', () => [ new User() ]);

001

Only custom class instanses inside sample array are allowed!

Invalid

const usersJTC = new JTC('Tester', () => [ { name: '', age: 0 } ]);

Valid

class User {
   public name = '';
   public age = 0;
}

const usersJTC = new JTC('Tester', () => [ new User() ]);

002

Functions are not allowed inside simple objects, use class instead, function under key: ${key}

Invalid

const usersJTC = new JTC('Tester', () => {
   const user = new User();
   user.notes = [{
      text: '',
      timestamp: 0,
      isDone: false,
      getSatus: () => {
         return `I planned ${this.text} on ${new Date(this.timestamp).toLocaleString()} and this is ${this.isDone ? '' : 'not yet '} done`;
      }
   }];

   return [ user ];
});

Valid

class Note {
   public text = '';
   public timestamp = 0;
   public isDone = false;

   getSatus() {
      return `I planned ${this.text} on ${new Date(this.timestamp).toLocaleString()} and this is ${this.isDone ? '' : 'not yet '} done`;
   }
}

const usersJTC = new JTC('Tester', () => {
   const user = new User();
   user.notes = [ new Note() ];

   return [ user ];
});

003

Undefined or null are not allowed inside sample, bad value under key: ${key}

Invalid

class User {
   public name = null;
   public age = undefined;
}

const usersJTC = new JTC('Tester', () => [ new User() ]);

Valid

class User {
   public name = '';
   public age = 0;
}

const usersJTC = new JTC('Tester', () => [ new User() ]);

004

Arrays inside sample should have at least one value to detrmine what is expected, empty array under key: ${key}

Invalid

class User {
   public name = '';
   public age = 0;
   public remarks: string[] = [];
}

const usersJTC = new JTC('Tester', () => [ new User() ]);

Valid

class User {
   public name = '';
   public age = 0;
   public remarks: string[] = [];
}

const usersJTC = new JTC('Tester', () => {
   const user = new User();
   user.remarks = [ '' ];

   return [ user ];
});

005

Maps inside sample should have at least one value, bad value under key: ${key}

Invalid

class User {
   public name = '';
   public age = 0;
   @JTC.map public drinks: Drink = {};
}

interface Drink {
   [drink: string]: number;
}

const usersJTC = new JTC('Tester', () => [ new User() ]);

Valid

class User {
   public name = '';
   public age = 0;
   @JTC.map public drinks: Drink = {};
}
const usersJTC = new JTC('Tester', () => {
   const user = new User();
   user.drinks['drink'] = 0;

   return [ user ];
});

Versioning

1.1.0

  • Additional separation on valid and repaired objects in 'array' method response.
  • Type script check for object inheritance in sample function.

1.2.0

  • Factory method "JTC.getInstance(sample)" for instanse creation based on sample type;
  • Restriction to show more than 20 repaired elements in the logger, because of performance issues;
  • Parse duration added to the warning message;
  • Dependencies versions update
  • Ability to disable logging and "JTC.ignore" decorator removed due it breaks the ideology of "Fix the issue, not just mask it".

2.2.0

  • "JTC.date" decorator added - provides ability to convert string / number primitive into js Date during validation

2.3.0

  • Logs about "Pre / Post" repair state removed, due to its memory consumption and low useability