UNPKG

like-json

Version:

Stringify at perfect performance. +1200%

152 lines (126 loc) 6.1 kB
/* like-json (https://npmjs.com/package/like-json) Copyright 2019 Lucas Barrena Licensed under MIT (https://github.com/LuKks/like-json) */ (function () { 'use strict'; let like = { _json_cache: {} }; like.json = function (schema, options) { options = options || {}; function value (v, k) { //the next double checks seems equal but are different //for example, a = 'Some letters' and b = new String('Some letters') //a: the typeof is string and the instanceof is not String //b: the typeof is object and the instanceof is String //both are normal strings in JSON if (typeof v === 'string' || v instanceof String) { return '"\' + ' + k + (options.encode ? '.replace(/(\\\\|")/g, "\\\\$1")' : '') + ' + \'"'; } //the previous explanation about strings apply in the same way for numbers and booleans if (typeof v === 'number' || v instanceof Number) { return '\' + ' + (options.finite ? ('(isFinite(' + k + ') ? ' + k + ' : null)') : k) + ' + \''; } if (typeof v === 'boolean' || v instanceof Boolean) { return '\' + ' + k + ' + \''; } return undefined; } function iterate_object_props (base, parent) { //used multiple times so saved here let isArray = Array.isArray(base); //start the struct let struct = isArray ? '[' : '{'; //iterate the properties for (let k in base) { //we are using for in because "base" var can be object or array //then k var is string even if it is iterating an array if (isArray) { k = parseInt(k, 10); } //for example, you have arr = ['a', 'b'] //you can: arr.c = true; //even being an array you will have a prop that is not numeric //in that case, k var here will be NaN due the previous parseInt let isNumeric = isFinite(k); //the actual path to be combined with previous parents if it's the case let actual = isNumeric ? '[' + k + ']' : '.' + k; //key var is for the struct and only needed when it's an object //for example, { message: 'ok' }, the prop "message" is the key //another example, ['ok', 'ok', 'deny'], here there is no key to add let key = isArray ? '' : '"' + k + '":'; //the magic occurs here, checking values and types, in this way //we are adding these props to the struct being compatible with JSON if (base[k] === null) { //for example, if we are iterating [null, null] //then the struct var started with '[' //key var is empty because it's an array //so, here the add will be just 'null,' struct += key + 'null,'; //struct var here will be '[null,' //this explanation apply for every next checks/values //now you can jump to the return part (after loop) and see how it ends } else if (['function', 'symbol', 'undefined'].indexOf(typeof base[k]) !== -1) { if(isArray && isNumeric) { struct += key + 'null,'; } } else if (base[k] && typeof base[k].toJSON === 'function') { struct += key + "\"' + " + 'o' + parent + actual + ".toJSON() + '\"" + ','; } else if (typeof base[k] === 'object' && [String, Number, Boolean].indexOf(base[k].constructor) === -1) { //just to be clear, for example, you have to stringify: { data: { visits: 9, purchases: 1 } } //in the first moment "base" var is the entire object //and, for example, base[k] is base.data object //so here recursively we iterate this new "base" object that is base.data //but we add the parent keys, in this way we don't lose the props of the real base object struct += key + iterate_object_props(base[k], parent + actual) + ','; } else { //here the type it's an string, number, etc let v = value(base[k], 'o' + parent + actual); if (v !== undefined) { struct += key + v + ','; } } } //we always added a comma for the next values //but at the end we have an extra comma that is useless //and not always there is props to iterate so need to check if really there is a comma to remove if (struct[struct.length - 1] === ',') { struct = struct.slice(0, -1); } //this is like the start but in this case we close the object or array return struct + (isArray ? ']' : '}'); } //these checks are similar to the ones that are inside in the recursive function //but only for single values, that is, the schema is not object or array (no need to iterate) //also it's more simple doing the checks in this way instead of add more complexity in the function if (schema === null) { return Function('o', "return 'null';"); } if (['function', 'symbol', 'undefined'].indexOf(typeof schema) !== -1) { return Function('o', 'return undefined;'); } if (schema && typeof schema.toJSON === 'function') { return Function('o', "return '\"' + o.toJSON() + '\"';"); } //you can create an object with null prototype, so this check allow that case if (typeof schema === 'object' && [String, Number, Boolean].indexOf(schema.constructor) === -1) { return Function('o', "return '" + iterate_object_props(schema, '') + "';"); } return Function('o', "return '" + value(schema, 'o') + "';"); } like.stringify = function (obj, id, options) { if (!this._json_cache[id]) { this._json_cache[id] = this.json(obj, options); } return this._json_cache[id](obj); } if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = like; } else if (typeof window !== 'undefined') { window.like = like; } else { console.log('Unable to export.'); } })();