jsontoclass
v2.3.1
Published
JSON to class mapper
Downloads
5
Maintainers
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