Comparing version 1.1.0 to 1.2.0
@@ -1,3 +0,1 @@ | ||
'use strict'; | ||
module.exports = require('./lib/FastJson'); |
@@ -1,53 +0,63 @@ | ||
'use strict'; | ||
const EventEmitter = require('events'); | ||
var OPEN_BRACE = '{'.charCodeAt(0); | ||
var CLOSE_BRACE = '}'.charCodeAt(0); | ||
var OPEN_BRACKET = '['.charCodeAt(0); | ||
var CLOSE_BRACKET = ']'.charCodeAt(0); | ||
var QUOTE = '"'.charCodeAt(0); | ||
var SPACE = ' '.charCodeAt(0); | ||
var NEW_LINE = '\n'.charCodeAt(0); | ||
var CARRIAGE_RETURN = '\r'.charCodeAt(0); | ||
var TAB = '\t'.charCodeAt(0); | ||
var COLON = ':'.charCodeAt(0); | ||
var COMMA = ','.charCodeAt(0); | ||
var BACKSLASH = '\\'.charCodeAt(0); | ||
const OPEN_BRACE = '{'.charCodeAt(0); | ||
const CLOSE_BRACE = '}'.charCodeAt(0); | ||
const OPEN_BRACKET = '['.charCodeAt(0); | ||
const CLOSE_BRACKET = ']'.charCodeAt(0); | ||
const QUOTE = '"'.charCodeAt(0); | ||
const SPACE = ' '.charCodeAt(0); | ||
const NEW_LINE = '\n'.charCodeAt(0); | ||
const CARRIAGE_RETURN = '\r'.charCodeAt(0); | ||
const TAB = '\t'.charCodeAt(0); | ||
const COLON = ':'.charCodeAt(0); | ||
const COMMA = ','.charCodeAt(0); | ||
const BACKSLASH = '\\'.charCodeAt(0); | ||
const TYPE_ARRAY = 1; | ||
const TYPE_OBJECT = 2; | ||
const SEP = '/'; | ||
class FastJson { | ||
/** | ||
* @param {Object} [options] The fast-json configuration object. | ||
*/ | ||
constructor(options) { | ||
this._options = options || {}; | ||
this._stack = new Array(16); | ||
this._level = -1; | ||
this._stack = []; | ||
this._postColon = false; | ||
this._lastString = null; | ||
this._lastString = {}; | ||
this._skipped = false; | ||
this._events = new EventEmitter(); | ||
this._subPaths = new Set(['/']); | ||
this._isString = false; | ||
this._subPaths = new Set([SEP]); | ||
} | ||
/** | ||
* Adds a listener function for the provided path. | ||
* @param {Array|String} path The JSON path to get values. | ||
* @param {FastJson~jsonListener} listener The function called after finding the JSON path. | ||
*/ | ||
on(path, listener) { | ||
var processedPath = this._processPath(path); | ||
this._addToSubPaths(processedPath); | ||
this._events.on(processedPath, listener); | ||
const normPath = FastJson._normalizePath(path); | ||
this._addToSubPaths(normPath); | ||
this._events.on(normPath, listener); | ||
} | ||
/** | ||
* Start processing JSON using the defined paths in {@link FastJson#on} method. | ||
* @param {String|Buffer} data The JSON to process. | ||
*/ | ||
write(data) { | ||
this._isString = typeof data === 'string'; | ||
for (var i = 0; i < data.length; i++) { | ||
switch (this._get(data, i)) { | ||
for (let i = 0; i < data.length && !this._skipped; i++) { | ||
switch (FastJson._get(data, i)) { | ||
case OPEN_BRACE: | ||
i = this._onOpenBrace(data, i); | ||
i = this._onOpenBlock(data, i, TYPE_OBJECT, OPEN_BRACE, CLOSE_BRACE); | ||
break; | ||
case OPEN_BRACKET: | ||
i = this._onOpenBracket(data, i); | ||
i = this._onOpenBlock(data, i, TYPE_ARRAY, OPEN_BRACKET, CLOSE_BRACKET); | ||
break; | ||
case CLOSE_BRACE: case CLOSE_BRACKET: | ||
this._onCloseBraceOrBracket(data, i); | ||
this._onCloseBlock(data, i); | ||
break; | ||
@@ -57,3 +67,3 @@ case QUOTE: | ||
break; | ||
case TAB : case CARRIAGE_RETURN : case NEW_LINE : case SPACE: | ||
case TAB: case CARRIAGE_RETURN: case NEW_LINE: case SPACE: | ||
break; | ||
@@ -70,46 +80,46 @@ case COLON: | ||
} | ||
} | ||
_onOpenBrace(data, index) { | ||
this._level++; | ||
var parent = this._resolveParentKey(data); | ||
var path = this._resolvePath(parent); | ||
if (!this._subPaths.has(path)) { | ||
this._level--; | ||
return this._skipBlock(data, index, OPEN_BRACE, CLOSE_BRACE); | ||
if (this._skipped) { | ||
this._skipCleanUp(); | ||
} | ||
} | ||
this._stack[this._level] = { | ||
type: 'object', | ||
start: index, | ||
parent: parent, | ||
path: path | ||
}; | ||
/** | ||
* Stop processing the last JSON provided in the {@link FastJson#write} method. | ||
*/ | ||
skip() { | ||
this._skipped = true; | ||
} | ||
_skipCleanUp() { | ||
this._stack = []; | ||
this._postColon = false; | ||
return index; | ||
this._skipped = false; | ||
} | ||
_onOpenBracket(data, index) { | ||
this._level++; | ||
var parent = this._resolveParentKey(data); | ||
var path = this._resolvePath(parent); | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} index | ||
* @param {String} type | ||
* @param {Number} openChar | ||
* @param {Number} closeChar | ||
* @returns {Number} | ||
* @private | ||
*/ | ||
_onOpenBlock(data, index, type, openChar, closeChar) { | ||
const path = this._resolvePath(data); | ||
if (!this._subPaths.has(path)) { | ||
this._level--; | ||
return this._skipBlock(data, index, OPEN_BRACKET, CLOSE_BRACKET); | ||
return FastJson._skipBlock(data, index, openChar, closeChar); | ||
} | ||
this._stack[this._level] = { | ||
type: 'array', | ||
this._stack.push({ | ||
// General | ||
type, | ||
start: index, | ||
parent: parent, | ||
path: path, | ||
index: 0 | ||
}; | ||
path, | ||
// TYPE_ARRAY | ||
index: 0, | ||
}); | ||
this._postColon = false; | ||
@@ -120,30 +130,38 @@ | ||
_onCloseBraceOrBracket(data, index) { | ||
var frame = this._stack[this._level]; | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} index | ||
* @private | ||
*/ | ||
_onCloseBlock(data, index) { | ||
const frame = this._stack.pop(); | ||
frame.end = index; | ||
if (this._hasListeners(frame.path)) { | ||
var result = data.slice(frame.start, frame.end + 1); | ||
this._events.emit(frame.path, result); | ||
this._events.emit(frame.path, data.slice(frame.start, frame.end + 1)); | ||
} | ||
this._level--; | ||
} | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} index | ||
* @returns {Number} | ||
* @private | ||
*/ | ||
_onQuote(data, index) { | ||
var frame = this._stack[this._level]; | ||
var str = { start: index + 1, end: this._parseString(data, index) }; | ||
var path; | ||
const frame = this._getFrame(); | ||
const strStart = index + 1; | ||
const strEnd = FastJson._parseString(data, index); | ||
if (this._postColon) { | ||
path = this._resolvePathForPrimitiveObject(data); | ||
const path = this._resolvePathForPrimitiveObject(data, frame); | ||
if (this._hasListeners(path)) { | ||
this._events.emit(path, data.slice(str.start, str.end)); | ||
this._events.emit(path, data.slice(strStart, strEnd)); | ||
} | ||
} else if (frame.type === 'array') { | ||
path = this._resolvePathForPrimitiveArray(data); | ||
} else if (frame.type === TYPE_ARRAY) { | ||
const path = FastJson._resolvePathForPrimitiveArray(frame); | ||
if (this._hasListeners(path)) { | ||
this._events.emit(path, data.slice(str.start, str.end)); | ||
this._events.emit(path, data.slice(strStart, strEnd)); | ||
} | ||
@@ -153,10 +171,11 @@ } | ||
this._postColon = false; | ||
this._lastString = str; | ||
this._lastString.start = strStart; | ||
this._lastString.end = strEnd; | ||
return index + (str.end - str.start + 1); | ||
return index + ((strEnd - strStart) + 1); | ||
} | ||
_onComma() { | ||
var frame = this._stack[this._level]; | ||
if (frame.type === 'array') { | ||
const frame = this._getFrame(); | ||
if (frame.type === TYPE_ARRAY) { | ||
frame.index++; | ||
@@ -166,9 +185,14 @@ } | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} index | ||
* @returns {Number} | ||
* @private | ||
*/ | ||
_onPrimitive(data, index) { | ||
var frame = this._stack[this._level]; | ||
var primEnd = this._parsePrimitive(data, index); | ||
var path; | ||
const frame = this._getFrame(); | ||
const primEnd = FastJson._parsePrimitive(data, index); | ||
if (this._postColon) { | ||
path = this._resolvePathForPrimitiveObject(data); | ||
const path = this._resolvePathForPrimitiveObject(data, frame); | ||
@@ -178,4 +202,4 @@ if (this._hasListeners(path)) { | ||
} | ||
} else if (frame.type === 'array') { | ||
path = this._resolvePathForPrimitiveArray(data); | ||
} else if (frame.type === TYPE_ARRAY) { | ||
const path = FastJson._resolvePathForPrimitiveArray(frame); | ||
@@ -192,12 +216,21 @@ if (this._hasListeners(path)) { | ||
_skipBlock(data, index, openChar, closeChar) { | ||
var blockDepth = 1; | ||
index++; | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} index | ||
* @param {Number} openChar | ||
* @param {Number} closeChar | ||
* @returns {Number} | ||
* @private | ||
*/ | ||
static _skipBlock(data, index, openChar, closeChar) { | ||
let blockDepth = 1; | ||
let i = index + 1; | ||
while (true) { | ||
switch (this._get(data, index)) { | ||
case QUOTE: | ||
var strEnd = this._parseString(data, index); | ||
index += strEnd - index; | ||
for (; blockDepth > 0; i++) { | ||
switch (FastJson._get(data, i)) { | ||
case QUOTE: { | ||
const strEnd = FastJson._parseString(data, i); | ||
i += strEnd - i; | ||
break; | ||
} | ||
case openChar: | ||
@@ -208,101 +241,129 @@ blockDepth++; | ||
blockDepth--; | ||
if (blockDepth === 0) { | ||
return index; | ||
} | ||
break; | ||
default: | ||
} | ||
} | ||
index++; | ||
} | ||
return i - 1; | ||
} | ||
_parseString(data, index) { | ||
index++; | ||
while (true) { | ||
switch (this._get(data, index)) { | ||
case QUOTE: return index; | ||
case BACKSLASH: index++; | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} index | ||
* @returns {Number} | ||
* @private | ||
*/ | ||
static _parseString(data, index) { | ||
for (let i = index + 1; ;i++) { | ||
switch (FastJson._get(data, i)) { | ||
case QUOTE: return i; | ||
case BACKSLASH: i++; break; | ||
default: | ||
} | ||
index++; | ||
} | ||
} | ||
_parsePrimitive(data, index) { | ||
while (true) { | ||
switch (this._get(data, index)) { | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} index | ||
* @returns {Number} | ||
* @private | ||
*/ | ||
static _parsePrimitive(data, index) { | ||
for (let i = index; ;i++) { | ||
switch (FastJson._get(data, i)) { | ||
case CLOSE_BRACKET: case CLOSE_BRACE: case COMMA: | ||
return index; | ||
return i; | ||
default: | ||
} | ||
index++; | ||
} | ||
} | ||
_resolveParentKey(data) { | ||
if (this._level <= 0) { | ||
return '/'; | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Object} frame | ||
* @returns {String} | ||
* @private | ||
*/ | ||
_getParentKey(data, frame) { | ||
if (frame.type === TYPE_ARRAY) { | ||
return frame.index; | ||
} | ||
var parentType = this._stack[this._level - 1].type; | ||
if (parentType === 'array') { | ||
return this._stack[this._level - 1].index; | ||
} | ||
return this._toString(data, this._lastString.start, this._lastString.end); | ||
return FastJson._toString(data, this._lastString.start, this._lastString.end); | ||
} | ||
_resolvePath(parentKey) { | ||
if (this._level === 0) { | ||
return parentKey; | ||
} else if (this._level === 1) { | ||
return this._stack[this._level - 1].path + parentKey; | ||
/** | ||
* @param {String|Buffer} data | ||
* @returns {String} | ||
* @private | ||
*/ | ||
_resolvePath(data) { | ||
if (this._stack.length === 0) { | ||
return SEP; | ||
} | ||
return this._stack[this._level - 1].path + '/' + parentKey; | ||
const frame = this._getFrame(); | ||
return `${frame.path}${this._getParentKey(data, frame)}${SEP}`; | ||
} | ||
_hasListeners(processedPath) { | ||
return this._events.listenerCount(processedPath) > 0; | ||
/** | ||
* @return {Object} | ||
* @private | ||
*/ | ||
_getFrame() { | ||
return this._stack[this._stack.length - 1]; | ||
} | ||
_resolvePathForPrimitiveObject(data) { | ||
if (this._level === 0) { | ||
return '/' + this._toString(data, this._lastString.start, this._lastString.end); | ||
} | ||
return this._stack[this._level].path + '/' + | ||
this._toString(data, this._lastString.start, this._lastString.end); | ||
/** | ||
* @param {String} path | ||
* @returns {Boolean} | ||
* @private | ||
*/ | ||
_hasListeners(path) { | ||
return this._events.listenerCount(path) > 0; | ||
} | ||
_resolvePathForPrimitiveArray(data) { | ||
if (this._level === 0) { | ||
return '/' + this._stack[this._level].index; | ||
} | ||
return this._stack[this._level].path + '/' + this._stack[this._level].index; | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Object} frame | ||
* @returns {String} | ||
* @private | ||
*/ | ||
_resolvePathForPrimitiveObject(data, frame) { | ||
return `${frame.path}${ | ||
FastJson._toString(data, this._lastString.start, this._lastString.end)}${SEP}`; | ||
} | ||
_processPath(path) { | ||
if (!path) { | ||
return '/'; | ||
} | ||
path = path.replace(/[\.\[]/g, '/'); | ||
path = path.replace(/\]/g, ''); | ||
return path[0] !== '/' ? '/' + path : path; | ||
/** | ||
* @param {Object} frame | ||
* @returns {String} | ||
* @private | ||
*/ | ||
static _resolvePathForPrimitiveArray(frame) { | ||
return `${frame.path}${frame.index}${SEP}`; | ||
} | ||
_addToSubPaths(processedPath) { | ||
var aux = ''; | ||
var tokens = processedPath.split('/'); | ||
/** | ||
* @param {String} path | ||
* @private | ||
*/ | ||
_addToSubPaths(path) { | ||
let subPath = SEP; | ||
const tokens = path.split(SEP); | ||
for (var i = 1; i < tokens.length; i++) { | ||
aux += '/' + tokens[i]; | ||
this._subPaths.add(aux); | ||
for (let i = 1; i < tokens.length - 1; i++) { | ||
subPath += `${tokens[i]}${SEP}`; | ||
this._subPaths.add(subPath); | ||
} | ||
} | ||
_get(data, index) { | ||
if (this._isString) { | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} index | ||
* @returns {Number} | ||
* @private | ||
*/ | ||
static _get(data, index) { | ||
if (typeof data === 'string') { | ||
return data.charCodeAt(index); | ||
@@ -314,4 +375,11 @@ } | ||
_toString(data, start, end) { | ||
if (this._isString) { | ||
/** | ||
* @param {String|Buffer} data | ||
* @param {Number} start | ||
* @param {Number} end | ||
* @returns {String} | ||
* @private | ||
*/ | ||
static _toString(data, start, end) { | ||
if (typeof data === 'string') { | ||
return data.slice(start, end); | ||
@@ -322,4 +390,25 @@ } | ||
} | ||
/** | ||
* @param {Array|String} origPath | ||
* @returns {String} | ||
* @private | ||
*/ | ||
static _normalizePath(origPath) { | ||
if (Array.isArray(origPath)) { | ||
return `${SEP}${origPath.join(SEP)}${SEP}`; | ||
} | ||
let path = origPath.replace(/[.[]/g, SEP); | ||
path = path.replace(/\]/g, '') + SEP; | ||
return !path.startsWith(SEP) ? `${SEP}${path}` : path; | ||
} | ||
} | ||
module.exports = FastJson; | ||
/** | ||
* @callback FastJson~jsonListener | ||
* @param {String|Buffer} value The found value type will depend of the type used in | ||
* {@link FastJson#write}. | ||
*/ |
{ | ||
"name": "fast-json", | ||
"description": "A lightning fast on the fly JSON parser", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"author": "Alejandro Santiago Nieto", | ||
"license": "MIT", | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"chai": "3.x.x", | ||
"jscs": "3.x.x", | ||
"jshint": "2.x.x", | ||
"mocha": "3.x.x" | ||
"chai": "^4.3.4", | ||
"eslint": "^7.26.0", | ||
"eslint-config-airbnb-base": "^14.2.1", | ||
"eslint-plugin-import": "^2.22.1", | ||
"jsdoc": "^3.6.6", | ||
"mocha": "^8.4.0", | ||
"nyc": "^15.1.0" | ||
}, | ||
@@ -21,6 +23,7 @@ "main": "index.js", | ||
"test": "mocha test/*.js", | ||
"jshint": "jshint index.js lib/*.js test/*.js", | ||
"jscs": "jscs index.js lib/*.js test/*.js", | ||
"jscs:fix": "jscs --fix index.js lib/*.js test/*.js", | ||
"check": "npm run test && npm run jshint && npm run jscs && npm outdated" | ||
"test:cover": "nyc --reporter=lcov --reporter=text-summary npm test", | ||
"lint": "eslint index.js lib/*.js test/*.js example/*.js", | ||
"lint:fix": "eslint index.js lib/*.js test/*.js example/*.js --fix", | ||
"check": "npm test && npm run lint && npm outdated", | ||
"doc": "jsdoc ./lib/ ./package.json ./README.md -d ./doc" | ||
}, | ||
@@ -27,0 +30,0 @@ "repository": { |
@@ -6,27 +6,25 @@ fast-json | ||
## Install | ||
``` | ||
npm install fast-json | ||
``` | ||
## Usage | ||
```javascript | ||
var FastJson = require('fast-json'); | ||
const FastJson = require('fast-json'); | ||
var data = JSON.stringify({ | ||
const data = JSON.stringify({ | ||
ireland: { | ||
people: [{ name: 'Alex' }, { name: 'John' }, { name: 'Cian' }] | ||
people: [{ name: 'Alex' }, { name: 'John' }, { name: 'Cian' }], | ||
}, | ||
spain: { | ||
people: [{ name: 'Antonio' }, { name: 'Juan' }, { name: 'Pedro' }] | ||
} | ||
people: [{ name: 'Antonio' }, { name: 'Juan' }, { name: 'Pedro' }], | ||
}, | ||
'unknown.country': { | ||
people: [{ name: 'Frank' }, { name: 'Paul' }], | ||
}, | ||
huge_list: [1, 2, 3], | ||
}); | ||
var fastJson = new FastJson(); | ||
const fastJson = new FastJson(); | ||
fastJson.on('ireland.people[0]', (value) => { | ||
console.log('ireland.people[0] ->', value); | ||
}); | ||
fastJson.on('spain', (value) => { | ||
console.log('spain ->', value); | ||
}); | ||
fastJson.on('ireland.people', (value) => { | ||
@@ -40,5 +38,12 @@ console.log('ireland.people ->', value); | ||
// Path as Array to allow keys with dots | ||
fastJson.on(['unknown.country', 'people', '0', 'name'], (value) => { | ||
console.log(['unknown.country', 'people', '0', 'name'], value); | ||
// Stop parsing JSON usefull when have all we need improving performance | ||
fastJson.skip(); | ||
}); | ||
fastJson.write(data); | ||
// or | ||
fastJson.write(new Buffer(data)); | ||
fastJson.write(Buffer.from(data)); | ||
``` | ||
@@ -56,4 +61,3 @@ | ||
* [**Feature**] Process wildcards in paths. | ||
* [**Feature**] Public method to cancel actual write(). | ||
* [**Documentation**] Document public interface and create branch gh-pages using *jsdoc*. | ||
* [**Documentation**] More real life testing and examples. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
11882
351
61
7
1