@discoveryjs/json-ext
Advanced tools
Comparing version 0.5.2 to 0.5.3
@@ -0,1 +1,8 @@ | ||
## 0.5.3 (2021-05-13) | ||
- Fixed `stringifyStream()` and `stringifyInfo()` to work properly when replacer is an allowlist | ||
- `parseChunked()` | ||
- Fixed wrong parse error when chunks are splitted on a whitespace inside an object or array (#6, @alexei-vedder) | ||
- Fixed corner cases when wrong placed or missed comma doesn't cause to parsing failure | ||
## 0.5.2 (2020-12-26) | ||
@@ -2,0 +9,0 @@ |
@@ -8,3 +8,3 @@ (function (global, factory) { | ||
var name = "@discoveryjs/json-ext"; | ||
var version = "0.5.2"; | ||
var version = "0.5.3"; | ||
var description = "A set of utilities that extend the use of JSON"; | ||
@@ -35,6 +35,6 @@ var keywords = [ | ||
"test:src": "npm test", | ||
"test:dist": "MODE=dist npm test && MODE=dist-min npm test", | ||
"test:dist": "cross-env MODE=dist npm test && cross-env MODE=dist-min npm test", | ||
"build-and-test": "npm run build && npm run test:dist", | ||
coverage: "nyc npm test", | ||
travis: "nyc npm run lint-and-test && npm run coveralls", | ||
travis: "nyc npm run lint-and-test && npm run build-and-test && npm run coveralls", | ||
coveralls: "nyc report --reporter=text-lcov | coveralls", | ||
@@ -51,2 +51,3 @@ prepublishOnly: "npm run build" | ||
coveralls: "^3.1.0", | ||
"cross-env": "^7.0.3", | ||
eslint: "^7.6.0", | ||
@@ -89,3 +90,3 @@ mocha: "^8.1.1", | ||
// https://tc39.es/ecma262/#table-json-single-character-escapes | ||
const escapableCharCodeSubstitution = { // JSON Single Character Escape Sequences | ||
const escapableCharCodeSubstitution$1 = { // JSON Single Character Escape Sequences | ||
0x08: '\\b', | ||
@@ -100,11 +101,11 @@ 0x09: '\\t', | ||
function isLeadingSurrogate(code) { | ||
function isLeadingSurrogate$1(code) { | ||
return code >= 0xD800 && code <= 0xDBFF; | ||
} | ||
function isTrailingSurrogate(code) { | ||
function isTrailingSurrogate$1(code) { | ||
return code >= 0xDC00 && code <= 0xDFFF; | ||
} | ||
function isReadableStream(value) { | ||
function isReadableStream$1(value) { | ||
return ( | ||
@@ -117,3 +118,3 @@ typeof value.pipe === 'function' && | ||
function replaceValue(holder, key, value, replacer) { | ||
function replaceValue$1(holder, key, value, replacer) { | ||
if (value && typeof value.toJSON === 'function') { | ||
@@ -146,3 +147,3 @@ value = value.toJSON(); | ||
function getTypeNative(value) { | ||
function getTypeNative$1(value) { | ||
if (value === null || typeof value !== 'object') { | ||
@@ -159,3 +160,3 @@ return PrimitiveType; | ||
function getTypeAsync(value) { | ||
function getTypeAsync$1(value) { | ||
if (value === null || typeof value !== 'object') { | ||
@@ -169,3 +170,3 @@ return PrimitiveType; | ||
if (isReadableStream(value)) { | ||
if (isReadableStream$1(value)) { | ||
return value._readableState.objectMode ? ReadableObjectType : ReadableStringType; | ||
@@ -181,3 +182,3 @@ } | ||
function normalizeReplacer(replacer) { | ||
function normalizeReplacer$1(replacer) { | ||
if (typeof replacer === 'function') { | ||
@@ -188,10 +189,11 @@ return replacer; | ||
if (Array.isArray(replacer)) { | ||
const whitelist = new Set(replacer | ||
.map(item => typeof item === 'string' || typeof item === 'number' ? String(item) : null) | ||
const allowlist = new Set(replacer | ||
.map(item => { | ||
const cls = item && item.constructor; | ||
return cls === String || cls === Number ? String(item) : null; | ||
}) | ||
.filter(item => typeof item === 'string') | ||
); | ||
whitelist.add(''); | ||
return (key, value) => whitelist.has(key) ? value : undefined; | ||
return [...allowlist]; | ||
} | ||
@@ -202,3 +204,3 @@ | ||
function normalizeSpace(space) { | ||
function normalizeSpace$1(space) { | ||
if (typeof space === 'number') { | ||
@@ -220,5 +222,5 @@ if (!Number.isFinite(space) || space < 1) { | ||
var utils = { | ||
escapableCharCodeSubstitution, | ||
isLeadingSurrogate, | ||
isTrailingSurrogate, | ||
escapableCharCodeSubstitution: escapableCharCodeSubstitution$1, | ||
isLeadingSurrogate: isLeadingSurrogate$1, | ||
isTrailingSurrogate: isTrailingSurrogate$1, | ||
type: { | ||
@@ -233,19 +235,19 @@ PRIMITIVE: PrimitiveType, | ||
isReadableStream, | ||
isReadableStream: isReadableStream$1, | ||
replaceValue: replaceValue$1, | ||
getTypeNative: getTypeNative$1, | ||
getTypeAsync: getTypeAsync$1, | ||
normalizeReplacer: normalizeReplacer$1, | ||
normalizeSpace: normalizeSpace$1 | ||
}; | ||
const { | ||
normalizeReplacer, | ||
normalizeSpace, | ||
replaceValue, | ||
getTypeNative, | ||
getTypeAsync, | ||
normalizeReplacer, | ||
normalizeSpace | ||
}; | ||
const { | ||
normalizeReplacer: normalizeReplacer$1, | ||
normalizeSpace: normalizeSpace$1, | ||
replaceValue: replaceValue$1, | ||
getTypeNative: getTypeNative$1, | ||
getTypeAsync: getTypeAsync$1, | ||
isLeadingSurrogate: isLeadingSurrogate$1, | ||
isTrailingSurrogate: isTrailingSurrogate$1, | ||
escapableCharCodeSubstitution: escapableCharCodeSubstitution$1, | ||
isLeadingSurrogate, | ||
isTrailingSurrogate, | ||
escapableCharCodeSubstitution, | ||
type: { | ||
@@ -261,3 +263,3 @@ PRIMITIVE, | ||
const charLength2048 = Array.from({ length: 2048 }).map((_, code) => { | ||
if (escapableCharCodeSubstitution$1.hasOwnProperty(code)) { | ||
if (escapableCharCodeSubstitution.hasOwnProperty(code)) { | ||
return 2; // \X | ||
@@ -282,7 +284,7 @@ } | ||
len += charLength2048[code]; | ||
} else if (isLeadingSurrogate$1(code)) { | ||
} else if (isLeadingSurrogate(code)) { | ||
len += 6; // \uXXXX since no pair with trailing surrogate yet | ||
prevLeadingSurrogate = true; | ||
continue; | ||
} else if (isTrailingSurrogate$1(code)) { | ||
} else if (isTrailingSurrogate(code)) { | ||
len = prevLeadingSurrogate | ||
@@ -322,3 +324,3 @@ ? len - 2 // surrogate pair (4 bytes), since we calculate prev leading surrogate as 6 bytes, substruct 2 bytes | ||
function spaceLength(space) { | ||
space = normalizeSpace$1(space); | ||
space = normalizeSpace(space); | ||
return typeof space === 'string' ? space.length : 0; | ||
@@ -333,3 +335,3 @@ } | ||
value = replaceValue$1(holder, key, value, replacer); | ||
value = replaceValue(holder, key, value, replacer); | ||
@@ -373,10 +375,10 @@ let type = getType(value); | ||
for (const property in value) { | ||
if (hasOwnProperty.call(value, property)) { | ||
for (const key in value) { | ||
if (hasOwnProperty.call(value, key) && (allowlist === null || allowlist.has(key))) { | ||
const prevLength = length; | ||
walk(value, property, value[property]); | ||
walk(value, key, value[key]); | ||
if (prevLength !== length) { | ||
// value is printed | ||
length += stringLength(property) + 1; // "property": | ||
length += stringLength(key) + 1; // "key": | ||
entries++; | ||
@@ -448,3 +450,10 @@ } | ||
replacer = normalizeReplacer$1(replacer); | ||
let allowlist = null; | ||
replacer = normalizeReplacer(replacer); | ||
if (Array.isArray(replacer)) { | ||
allowlist = new Set(replacer); | ||
replacer = null; | ||
} | ||
space = spaceLength(space); | ||
@@ -458,3 +467,3 @@ options = options || {}; | ||
const async = new Set(); | ||
const getType = options.async ? getTypeAsync$1 : getTypeNative$1; | ||
const getType = options.async ? getTypeAsync : getTypeNative; | ||
const root = { '': value }; | ||
@@ -480,3 +489,3 @@ let stop = false; | ||
const { isReadableStream: isReadableStream$1 } = utils; | ||
const { isReadableStream } = utils; | ||
@@ -516,3 +525,3 @@ | ||
if (isObject(chunkEmitter) && isReadableStream$1(chunkEmitter)) { | ||
if (isObject(chunkEmitter) && isReadableStream(chunkEmitter)) { | ||
return new Promise((resolve, reject) => { | ||
@@ -582,33 +591,70 @@ chunkEmitter | ||
this.pendingChunk = null; | ||
this.pos = 0; | ||
this.chunkOffset = 0; | ||
this.jsonParseOffset = 0; | ||
} | ||
parseAndAppend(fragment, wrap) { | ||
// Append new entries or elements | ||
if (this.stack[this.lastFlushDepth - 1] === STACK_OBJECT) { | ||
if (wrap) { | ||
this.jsonParseOffset--; | ||
fragment = '{' + fragment + '}'; | ||
} | ||
Object.assign(this.valueStack.value, JSON.parse(fragment)); | ||
} else { | ||
if (wrap) { | ||
this.jsonParseOffset--; | ||
fragment = '[' + fragment + ']'; | ||
} | ||
append(this.valueStack.value, JSON.parse(fragment)); | ||
} | ||
} | ||
prepareAddition(fragment) { | ||
const { value } = this.valueStack; | ||
const expectComma = Array.isArray(value) | ||
? value.length !== 0 | ||
: Object.keys(value).length !== 0; | ||
if (expectComma) { | ||
// Skip a comma at the beginning of fragment, otherwise it would | ||
// fail to parse | ||
if (fragment[0] === ',') { | ||
this.jsonParseOffset++; | ||
return fragment.slice(1); | ||
} | ||
// When value (an object or array) is not empty and a fragment | ||
// doesn't start with a comma, a single valid fragment starting | ||
// is a closing bracket. If it's not, a prefix is adding to fail | ||
// parsing. Otherwise, the sequence of chunks can be successfully | ||
// parsed, although it should not, e.g. ["[{}", "{}]"] | ||
if (fragment[0] !== '}' && fragment[0] !== ']') { | ||
this.jsonParseOffset -= 3; | ||
return '[[]' + fragment; | ||
} | ||
} | ||
return fragment; | ||
} | ||
flush(chunk, start, end) { | ||
let fragment = chunk.slice(start, end); | ||
this.jsonParseOffset = this.pos; // using for position correction in JSON.parse() error if any | ||
// Save position correction an error in JSON.parse() if any | ||
this.jsonParseOffset = this.chunkOffset + start; | ||
// Prepend pending chunk if any | ||
if (this.pendingChunk !== null) { | ||
fragment = this.pendingChunk + fragment; | ||
this.jsonParseOffset -= this.pendingChunk.length; | ||
this.pendingChunk = null; | ||
} | ||
// Skip a comma at the beginning if any | ||
if (fragment[0] === ',') { | ||
fragment = fragment.slice(1); | ||
this.jsonParseOffset++; | ||
} | ||
if (this.flushDepth === this.lastFlushDepth) { | ||
// Depth didn't changed, so it's a root value or entry/element set | ||
if (this.flushDepth > 0) { | ||
this.jsonParseOffset--; | ||
// Append new entries or elements | ||
if (this.stack[this.flushDepth - 1] === STACK_OBJECT) { | ||
Object.assign(this.valueStack.value, JSON.parse('{' + fragment + '}')); | ||
} else { | ||
append(this.valueStack.value, JSON.parse('[' + fragment + ']')); | ||
} | ||
this.parseAndAppend(this.prepareAddition(fragment), true); | ||
} else { | ||
@@ -636,10 +682,3 @@ // That's an entire value on a top level | ||
} else { | ||
this.jsonParseOffset--; | ||
// Parse fragment and append to current value | ||
if (this.stack[this.lastFlushDepth - 1] === STACK_OBJECT) { | ||
Object.assign(this.valueStack.value, JSON.parse('{' + fragment + '}')); | ||
} else { | ||
append(this.valueStack.value, JSON.parse('[' + fragment + ']')); | ||
} | ||
this.parseAndAppend(this.prepareAddition(fragment), true); | ||
} | ||
@@ -667,3 +706,5 @@ | ||
} | ||
} else { // this.flushDepth < this.lastFlushDepth | ||
} else /* this.flushDepth < this.lastFlushDepth */ { | ||
fragment = this.prepareAddition(fragment); | ||
// Add missed opening brackets/parentheses | ||
@@ -675,7 +716,3 @@ for (let i = this.lastFlushDepth - 1; i >= this.flushDepth; i--) { | ||
if (this.stack[this.lastFlushDepth - 1] === STACK_OBJECT) { | ||
Object.assign(this.valueStack.value, JSON.parse(fragment)); | ||
} else { | ||
append(this.valueStack.value, JSON.parse(fragment)); | ||
} | ||
this.parseAndAppend(fragment, false); | ||
@@ -687,3 +724,2 @@ for (let i = this.lastFlushDepth - 1; i >= this.flushDepth; i--) { | ||
this.pos += end - start; | ||
this.lastFlushDepth = this.flushDepth; | ||
@@ -774,3 +810,3 @@ } | ||
case 0x7B: /* { */ | ||
// begin object | ||
// Open an object | ||
flushPoint = i + 1; | ||
@@ -781,3 +817,3 @@ this.stack[this.flushDepth++] = STACK_OBJECT; | ||
case 0x5B: /* [ */ | ||
// begin array | ||
// Open an array | ||
flushPoint = i + 1; | ||
@@ -789,3 +825,3 @@ this.stack[this.flushDepth++] = STACK_ARRAY; | ||
case 0x7D: /* } */ | ||
// end object or array | ||
// Close an object or array | ||
flushPoint = i + 1; | ||
@@ -800,2 +836,17 @@ this.flushDepth--; | ||
break; | ||
case 0x09: /* \t */ | ||
case 0x0A: /* \n */ | ||
case 0x0D: /* \r */ | ||
case 0x20: /* space */ | ||
// Move points forward when they points on current position and it's a whitespace | ||
if (lastFlushPoint === i) { | ||
lastFlushPoint++; | ||
} | ||
if (flushPoint === i) { | ||
flushPoint++; | ||
} | ||
break; | ||
} | ||
@@ -808,10 +859,16 @@ } | ||
// Produce pendingChunk if any | ||
// Produce pendingChunk if something left | ||
if (flushPoint < chunkLength) { | ||
const newPending = chunk.slice(flushPoint, chunkLength); | ||
if (this.pendingChunk !== null) { | ||
// When there is already a pending chunk then no flush happened, | ||
// appending entire chunk to pending one | ||
this.pendingChunk += chunk; | ||
} else { | ||
// Create a pending chunk, it will start with non-whitespace since | ||
// flushPoint was moved forward away from whitespaces on scan | ||
this.pendingChunk = chunk.slice(flushPoint, chunkLength); | ||
} | ||
} | ||
this.pendingChunk = this.pendingChunk !== null | ||
? this.pendingChunk + newPending | ||
: newPending; | ||
} | ||
this.chunkOffset += chunkLength; | ||
} | ||
@@ -821,6 +878,3 @@ | ||
if (this.pendingChunk !== null) { | ||
if (/[^ \t\r\n]/.test(this.pendingChunk)) { | ||
this.flush('', 0, 0); | ||
} | ||
this.flush('', 0, 0); | ||
this.pendingChunk = null; | ||
@@ -827,0 +881,0 @@ } |
@@ -1,1 +0,1 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).jsonExt=e()}(this,(function(){"use strict";function t(t){return"function"==typeof t.pipe&&"function"==typeof t._read&&"object"==typeof t._readableState&&null!==t._readableState}var e={escapableCharCodeSubstitution:{8:"\\b",9:"\\t",10:"\\n",12:"\\f",13:"\\r",34:'\\"',92:"\\\\"},isLeadingSurrogate:function(t){return t>=55296&&t<=56319},isTrailingSurrogate:function(t){return t>=56320&&t<=57343},type:{PRIMITIVE:1,PROMISE:4,ARRAY:3,OBJECT:2,STRING_STREAM:5,OBJECT_STREAM:6},isReadableStream:t,replaceValue:function(t,e,s,n){switch(s&&"function"==typeof s.toJSON&&(s=s.toJSON()),null!==n&&(s=n.call(t,String(e),s)),typeof s){case"function":case"symbol":s=void 0;break;case"object":if(null!==s){const t=s.constructor;t!==String&&t!==Number&&t!==Boolean||(s=s.valueOf())}}return s},getTypeNative:function(t){return null===t||"object"!=typeof t?1:Array.isArray(t)?3:2},getTypeAsync:function(e){return null===e||"object"!=typeof e?1:"function"==typeof e.then?4:t(e)?e._readableState.objectMode?6:5:Array.isArray(e)?3:2},normalizeReplacer:function(t){if("function"==typeof t)return t;if(Array.isArray(t)){const e=new Set(t.map((t=>"string"==typeof t||"number"==typeof t?String(t):null)).filter((t=>"string"==typeof t)));return e.add(""),(t,s)=>e.has(t)?s:void 0}return null},normalizeSpace:function(t){return"number"==typeof t?!(!Number.isFinite(t)||t<1)&&" ".repeat(Math.min(t,10)):"string"==typeof t&&t.slice(0,10)||!1}};const{normalizeReplacer:s,normalizeSpace:n,replaceValue:i,getTypeNative:a,getTypeAsync:r,isLeadingSurrogate:l,isTrailingSurrogate:h,escapableCharCodeSubstitution:u,type:{PRIMITIVE:o,OBJECT:c,ARRAY:f,PROMISE:p,STRING_STREAM:g,OBJECT_STREAM:d}}=e,S=Array.from({length:2048}).map(((t,e)=>u.hasOwnProperty(e)?2:e<32?6:e<128?1:2));function y(t){let e=0,s=!1;for(let n=0;n<t.length;n++){const i=t.charCodeAt(n);if(i<2048)e+=S[i];else{if(l(i)){e+=6,s=!0;continue}h(i)?e=s?e-2:e+6:e+=3}s=!1}return e+2}var b=TextDecoder;const{isReadableStream:k}=e,v=new b;function m(t){return null!==t&&"object"==typeof t}function O(t,e){return"SyntaxError"===t.name&&e.jsonParseOffset&&(t.message=t.message.replace(/at position (\d+)/,((t,s)=>"at position "+(Number(s)+e.jsonParseOffset)))),t}function D(t,e){const s=t.length;t.length+=e.length;for(let n=0;n<e.length;n++)t[s+n]=e[n]}class w{constructor(){this.value=void 0,this.valueStack=null,this.stack=new Array(100),this.lastFlushDepth=0,this.flushDepth=0,this.stateString=!1,this.stateStringEscape=!1,this.pendingByteSeq=null,this.pendingChunk=null,this.pos=0,this.jsonParseOffset=0}flush(t,e,s){let n=t.slice(e,s);if(this.jsonParseOffset=this.pos,null!==this.pendingChunk&&(n=this.pendingChunk+n,this.pendingChunk=null),","===n[0]&&(n=n.slice(1),this.jsonParseOffset++),this.flushDepth===this.lastFlushDepth)this.flushDepth>0?(this.jsonParseOffset--,1===this.stack[this.flushDepth-1]?Object.assign(this.valueStack.value,JSON.parse("{"+n+"}")):D(this.valueStack.value,JSON.parse("["+n+"]"))):(this.value=JSON.parse(n),this.valueStack={value:this.value,prev:null});else if(this.flushDepth>this.lastFlushDepth){for(let t=this.flushDepth-1;t>=this.lastFlushDepth;t--)n+=1===this.stack[t]?"}":"]";0===this.lastFlushDepth?(this.value=JSON.parse(n),this.valueStack={value:this.value,prev:null}):(this.jsonParseOffset--,1===this.stack[this.lastFlushDepth-1]?Object.assign(this.valueStack.value,JSON.parse("{"+n+"}")):D(this.valueStack.value,JSON.parse("["+n+"]")));for(let t=this.lastFlushDepth||1;t<this.flushDepth;t++){let e=this.valueStack.value;if(1===this.stack[t-1]){let t;for(t in e);e=e[t]}else e=e[e.length-1];this.valueStack={value:e,prev:this.valueStack}}}else{for(let t=this.lastFlushDepth-1;t>=this.flushDepth;t--)this.jsonParseOffset--,n=(1===this.stack[t]?"{":"[")+n;1===this.stack[this.lastFlushDepth-1]?Object.assign(this.valueStack.value,JSON.parse(n)):D(this.valueStack.value,JSON.parse(n));for(let t=this.lastFlushDepth-1;t>=this.flushDepth;t--)this.valueStack=this.valueStack.prev}this.pos+=s-e,this.lastFlushDepth=this.flushDepth}push(t){if("string"!=typeof t){if(null!==this.pendingByteSeq){const e=t;(t=new Uint8Array(this.pendingByteSeq.length+e.length)).set(this.pendingByteSeq),t.set(e,this.pendingByteSeq.length),this.pendingByteSeq=null}if(t[t.length-1]>127)for(let e=0;e<t.length;e++){const s=t[t.length-1-e];if(s>>6==3){e++,(4!==e&&s>>3==30||3!==e&&s>>4==14||2!==e&&s>>5==6)&&(this.pendingByteSeq=t.slice(t.length-e),t=t.slice(0,-e));break}}t=v.decode(t)}const e=t.length;let s=0,n=0;t:for(let i=0;i<e;i++){if(this.stateString){for(;i<e;i++)if(this.stateStringEscape)this.stateStringEscape=!1;else switch(t.charCodeAt(i)){case 34:this.stateString=!1;continue t;case 92:this.stateStringEscape=!0}break}switch(t.charCodeAt(i)){case 34:this.stateString=!0,this.stateStringEscape=!1;break;case 44:n=i;break;case 123:n=i+1,this.stack[this.flushDepth++]=1;break;case 91:n=i+1,this.stack[this.flushDepth++]=2;break;case 93:case 125:n=i+1,this.flushDepth--,this.flushDepth<this.lastFlushDepth&&(this.flush(t,s,n),s=n)}}if(n>s&&this.flush(t,s,n),n<e){const s=t.slice(n,e);this.pendingChunk=null!==this.pendingChunk?this.pendingChunk+s:s}}finish(){return null!==this.pendingChunk&&(/[^ \t\r\n]/.test(this.pendingChunk)&&this.flush("",0,0),this.pendingChunk=null),this.value}}return{version:"0.5.2",stringifyInfo:function(t,e,l,h){e=s(e),l=function(t){return"string"==typeof(t=n(t))?t.length:0}(l),h=h||{};const u=new Map,S=new Set,b=new Set,k=new Set,v=new Set,m=h.async?r:a,O={"":t};let D=!1,w=0;return function t(s,n,a){if(D)return;a=i(s,n,a,e);let r=m(a);if(r!==o&&S.has(a))return k.add(a),w+=4,void(h.continueOnCircular||(D=!0));switch(r){case o:void 0!==a||Array.isArray(s)?w+=function(t){switch(typeof t){case"string":return y(t);case"number":return Number.isFinite(t)?String(t).length:4;case"boolean":return t?4:5;case"undefined":case"object":return 4;default:return 0}}(a):s===O&&(w+=9);break;case c:{if(u.has(a)){b.add(a),w+=u.get(a);break}const e=w;let s=0;w+=2,S.add(a);for(const e in a)if(hasOwnProperty.call(a,e)){const n=w;t(a,e,a[e]),n!==w&&(w+=y(e)+1,s++)}s>1&&(w+=s-1),S.delete(a),l>0&&s>0&&(w+=(1+(S.size+1)*l+1)*s,w+=1+S.size*l),u.set(a,w-e);break}case f:{if(u.has(a)){b.add(a),w+=u.get(a);break}const e=w;w+=2,S.add(a);for(let e=0;e<a.length;e++)t(a,e,a[e]);a.length>1&&(w+=a.length-1),S.delete(a),l>0&&a.length>0&&(w+=(1+(S.size+1)*l)*a.length,w+=1+S.size*l),u.set(a,w-e);break}case p:case g:v.add(a);break;case d:w+=2,v.add(a)}}(O,"",t),{minLength:isNaN(w)?1/0:w,circular:[...k],duplicate:[...b],async:[...v]}},stringifyStream:()=>{throw new Error("Method is not supported")},parseChunked:function(t){let e=new w;if(m(t)&&k(t))return new Promise(((s,n)=>{t.on("data",(t=>{try{e.push(t)}catch(t){n(O(t,e)),e=null}})).on("error",(t=>{e=null,n(t)})).on("end",(()=>{try{s(e.finish())}catch(t){n(O(t,e))}finally{e=null}}))}));if("function"==typeof t){const s=t();if(m(s)&&(Symbol.iterator in s||Symbol.asyncIterator in s))return new Promise((async(t,n)=>{try{for await(const t of s)e.push(t);t(e.finish())}catch(t){n(O(t,e))}finally{e=null}}))}throw new Error("Chunk emitter should be readable stream, generator, async generator or function returning an iterable object")}}})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).jsonExt=t()}(this,(function(){"use strict";function e(e){return"function"==typeof e.pipe&&"function"==typeof e._read&&"object"==typeof e._readableState&&null!==e._readableState}var t={escapableCharCodeSubstitution:{8:"\\b",9:"\\t",10:"\\n",12:"\\f",13:"\\r",34:'\\"',92:"\\\\"},isLeadingSurrogate:function(e){return e>=55296&&e<=56319},isTrailingSurrogate:function(e){return e>=56320&&e<=57343},type:{PRIMITIVE:1,PROMISE:4,ARRAY:3,OBJECT:2,STRING_STREAM:5,OBJECT_STREAM:6},isReadableStream:e,replaceValue:function(e,t,s,n){switch(s&&"function"==typeof s.toJSON&&(s=s.toJSON()),null!==n&&(s=n.call(e,String(t),s)),typeof s){case"function":case"symbol":s=void 0;break;case"object":if(null!==s){const e=s.constructor;e!==String&&e!==Number&&e!==Boolean||(s=s.valueOf())}}return s},getTypeNative:function(e){return null===e||"object"!=typeof e?1:Array.isArray(e)?3:2},getTypeAsync:function(t){return null===t||"object"!=typeof t?1:"function"==typeof t.then?4:e(t)?t._readableState.objectMode?6:5:Array.isArray(t)?3:2},normalizeReplacer:function(e){return"function"==typeof e?e:Array.isArray(e)?[...new Set(e.map((e=>{const t=e&&e.constructor;return t===String||t===Number?String(e):null})).filter((e=>"string"==typeof e)))]:null},normalizeSpace:function(e){return"number"==typeof e?!(!Number.isFinite(e)||e<1)&&" ".repeat(Math.min(e,10)):"string"==typeof e&&e.slice(0,10)||!1}};const{normalizeReplacer:s,normalizeSpace:n,replaceValue:i,getTypeNative:r,getTypeAsync:a,isLeadingSurrogate:l,isTrailingSurrogate:h,escapableCharCodeSubstitution:u,type:{PRIMITIVE:o,OBJECT:c,ARRAY:f,PROMISE:p,STRING_STREAM:d,OBJECT_STREAM:g}}=t,y=Array.from({length:2048}).map(((e,t)=>u.hasOwnProperty(t)?2:t<32?6:t<128?1:2));function S(e){let t=0,s=!1;for(let n=0;n<e.length;n++){const i=e.charCodeAt(n);if(i<2048)t+=y[i];else{if(l(i)){t+=6,s=!0;continue}h(i)?t=s?t-2:t+6:t+=3}s=!1}return t+2}var b=TextDecoder;const{isReadableStream:k}=t,A=new b;function v(e){return null!==e&&"object"==typeof e}function m(e,t){return"SyntaxError"===e.name&&t.jsonParseOffset&&(e.message=e.message.replace(/at position (\d+)/,((e,s)=>"at position "+(Number(s)+t.jsonParseOffset)))),e}class O{constructor(){this.value=void 0,this.valueStack=null,this.stack=new Array(100),this.lastFlushDepth=0,this.flushDepth=0,this.stateString=!1,this.stateStringEscape=!1,this.pendingByteSeq=null,this.pendingChunk=null,this.chunkOffset=0,this.jsonParseOffset=0}parseAndAppend(e,t){1===this.stack[this.lastFlushDepth-1]?(t&&(this.jsonParseOffset--,e="{"+e+"}"),Object.assign(this.valueStack.value,JSON.parse(e))):(t&&(this.jsonParseOffset--,e="["+e+"]"),function(e,t){const s=e.length;e.length+=t.length;for(let n=0;n<t.length;n++)e[s+n]=t[n]}(this.valueStack.value,JSON.parse(e)))}prepareAddition(e){const{value:t}=this.valueStack;if(Array.isArray(t)?0!==t.length:0!==Object.keys(t).length){if(","===e[0])return this.jsonParseOffset++,e.slice(1);if("}"!==e[0]&&"]"!==e[0])return this.jsonParseOffset-=3,"[[]"+e}return e}flush(e,t,s){let n=e.slice(t,s);if(this.jsonParseOffset=this.chunkOffset+t,null!==this.pendingChunk&&(n=this.pendingChunk+n,this.jsonParseOffset-=this.pendingChunk.length,this.pendingChunk=null),this.flushDepth===this.lastFlushDepth)this.flushDepth>0?this.parseAndAppend(this.prepareAddition(n),!0):(this.value=JSON.parse(n),this.valueStack={value:this.value,prev:null});else if(this.flushDepth>this.lastFlushDepth){for(let e=this.flushDepth-1;e>=this.lastFlushDepth;e--)n+=1===this.stack[e]?"}":"]";0===this.lastFlushDepth?(this.value=JSON.parse(n),this.valueStack={value:this.value,prev:null}):this.parseAndAppend(this.prepareAddition(n),!0);for(let e=this.lastFlushDepth||1;e<this.flushDepth;e++){let t=this.valueStack.value;if(1===this.stack[e-1]){let e;for(e in t);t=t[e]}else t=t[t.length-1];this.valueStack={value:t,prev:this.valueStack}}}else{n=this.prepareAddition(n);for(let e=this.lastFlushDepth-1;e>=this.flushDepth;e--)this.jsonParseOffset--,n=(1===this.stack[e]?"{":"[")+n;this.parseAndAppend(n,!1);for(let e=this.lastFlushDepth-1;e>=this.flushDepth;e--)this.valueStack=this.valueStack.prev}this.lastFlushDepth=this.flushDepth}push(e){if("string"!=typeof e){if(null!==this.pendingByteSeq){const t=e;(e=new Uint8Array(this.pendingByteSeq.length+t.length)).set(this.pendingByteSeq),e.set(t,this.pendingByteSeq.length),this.pendingByteSeq=null}if(e[e.length-1]>127)for(let t=0;t<e.length;t++){const s=e[e.length-1-t];if(s>>6==3){t++,(4!==t&&s>>3==30||3!==t&&s>>4==14||2!==t&&s>>5==6)&&(this.pendingByteSeq=e.slice(e.length-t),e=e.slice(0,-t));break}}e=A.decode(e)}const t=e.length;let s=0,n=0;e:for(let i=0;i<t;i++){if(this.stateString){for(;i<t;i++)if(this.stateStringEscape)this.stateStringEscape=!1;else switch(e.charCodeAt(i)){case 34:this.stateString=!1;continue e;case 92:this.stateStringEscape=!0}break}switch(e.charCodeAt(i)){case 34:this.stateString=!0,this.stateStringEscape=!1;break;case 44:n=i;break;case 123:n=i+1,this.stack[this.flushDepth++]=1;break;case 91:n=i+1,this.stack[this.flushDepth++]=2;break;case 93:case 125:n=i+1,this.flushDepth--,this.flushDepth<this.lastFlushDepth&&(this.flush(e,s,n),s=n);break;case 9:case 10:case 13:case 32:s===i&&s++,n===i&&n++}}n>s&&this.flush(e,s,n),n<t&&(null!==this.pendingChunk?this.pendingChunk+=e:this.pendingChunk=e.slice(n,t)),this.chunkOffset+=t}finish(){return null!==this.pendingChunk&&(this.flush("",0,0),this.pendingChunk=null),this.value}}return{version:"0.5.3",stringifyInfo:function(e,t,l,h){let u=null;t=s(t),Array.isArray(t)&&(u=new Set(t),t=null),l=function(e){return"string"==typeof(e=n(e))?e.length:0}(l),h=h||{};const y=new Map,b=new Set,k=new Set,A=new Set,v=new Set,m=h.async?a:r,O={"":e};let w=!1,D=0;return function e(s,n,r){if(w)return;r=i(s,n,r,t);let a=m(r);if(a!==o&&b.has(r))return A.add(r),D+=4,void(h.continueOnCircular||(w=!0));switch(a){case o:void 0!==r||Array.isArray(s)?D+=function(e){switch(typeof e){case"string":return S(e);case"number":return Number.isFinite(e)?String(e).length:4;case"boolean":return e?4:5;case"undefined":case"object":return 4;default:return 0}}(r):s===O&&(D+=9);break;case c:{if(y.has(r)){k.add(r),D+=y.get(r);break}const t=D;let s=0;D+=2,b.add(r);for(const t in r)if(hasOwnProperty.call(r,t)&&(null===u||u.has(t))){const n=D;e(r,t,r[t]),n!==D&&(D+=S(t)+1,s++)}s>1&&(D+=s-1),b.delete(r),l>0&&s>0&&(D+=(1+(b.size+1)*l+1)*s,D+=1+b.size*l),y.set(r,D-t);break}case f:{if(y.has(r)){k.add(r),D+=y.get(r);break}const t=D;D+=2,b.add(r);for(let t=0;t<r.length;t++)e(r,t,r[t]);r.length>1&&(D+=r.length-1),b.delete(r),l>0&&r.length>0&&(D+=(1+(b.size+1)*l)*r.length,D+=1+b.size*l),y.set(r,D-t);break}case p:case d:v.add(r);break;case g:D+=2,v.add(r)}}(O,"",e),{minLength:isNaN(D)?1/0:D,circular:[...A],duplicate:[...k],async:[...v]}},stringifyStream:()=>{throw new Error("Method is not supported")},parseChunked:function(e){let t=new O;if(v(e)&&k(e))return new Promise(((s,n)=>{e.on("data",(e=>{try{t.push(e)}catch(e){n(m(e,t)),t=null}})).on("error",(e=>{t=null,n(e)})).on("end",(()=>{try{s(t.finish())}catch(e){n(m(e,t))}finally{t=null}}))}));if("function"==typeof e){const s=e();if(v(s)&&(Symbol.iterator in s||Symbol.asyncIterator in s))return new Promise((async(e,n)=>{try{for await(const e of s)t.push(e);e(t.finish())}catch(e){n(m(e,t))}finally{t=null}}))}throw new Error("Chunk emitter should be readable stream, generator, async generator or function returning an iterable object")}}})); |
{ | ||
"name": "@discoveryjs/json-ext", | ||
"version": "0.5.2", | ||
"version": "0.5.3", | ||
"description": "A set of utilities that extend the use of JSON", | ||
@@ -29,6 +29,6 @@ "keywords": [ | ||
"test:src": "npm test", | ||
"test:dist": "MODE=dist npm test && MODE=dist-min npm test", | ||
"test:dist": "cross-env MODE=dist npm test && cross-env MODE=dist-min npm test", | ||
"build-and-test": "npm run build && npm run test:dist", | ||
"coverage": "nyc npm test", | ||
"travis": "nyc npm run lint-and-test && npm run coveralls", | ||
"travis": "nyc npm run lint-and-test && npm run build-and-test && npm run coveralls", | ||
"coveralls": "nyc report --reporter=text-lcov | coveralls", | ||
@@ -44,2 +44,3 @@ "prepublishOnly": "npm run build" | ||
"coveralls": "^3.1.0", | ||
"cross-env": "^7.0.3", | ||
"eslint": "^7.6.0", | ||
@@ -46,0 +47,0 @@ "mocha": "^8.1.1", |
@@ -6,2 +6,3 @@ # json-ext | ||
[![Coverage Status](https://coveralls.io/repos/github/discoveryjs/json-ext/badge.svg?branch=master)](https://coveralls.io/github/discoveryjs/json-ext?) | ||
[![NPM Downloads](https://img.shields.io/npm/dm/@discoveryjs/json-ext.svg)](https://www.npmjs.com/package/@discoveryjs/json-ext) | ||
@@ -8,0 +9,0 @@ A set of utilities that extend the use of JSON. Designed to be fast and memory efficient |
@@ -101,33 +101,70 @@ const { isReadableStream } = require('./utils'); | ||
this.pendingChunk = null; | ||
this.pos = 0; | ||
this.chunkOffset = 0; | ||
this.jsonParseOffset = 0; | ||
} | ||
parseAndAppend(fragment, wrap) { | ||
// Append new entries or elements | ||
if (this.stack[this.lastFlushDepth - 1] === STACK_OBJECT) { | ||
if (wrap) { | ||
this.jsonParseOffset--; | ||
fragment = '{' + fragment + '}'; | ||
} | ||
Object.assign(this.valueStack.value, JSON.parse(fragment)); | ||
} else { | ||
if (wrap) { | ||
this.jsonParseOffset--; | ||
fragment = '[' + fragment + ']'; | ||
} | ||
append(this.valueStack.value, JSON.parse(fragment)); | ||
} | ||
} | ||
prepareAddition(fragment) { | ||
const { value } = this.valueStack; | ||
const expectComma = Array.isArray(value) | ||
? value.length !== 0 | ||
: Object.keys(value).length !== 0; | ||
if (expectComma) { | ||
// Skip a comma at the beginning of fragment, otherwise it would | ||
// fail to parse | ||
if (fragment[0] === ',') { | ||
this.jsonParseOffset++; | ||
return fragment.slice(1); | ||
} | ||
// When value (an object or array) is not empty and a fragment | ||
// doesn't start with a comma, a single valid fragment starting | ||
// is a closing bracket. If it's not, a prefix is adding to fail | ||
// parsing. Otherwise, the sequence of chunks can be successfully | ||
// parsed, although it should not, e.g. ["[{}", "{}]"] | ||
if (fragment[0] !== '}' && fragment[0] !== ']') { | ||
this.jsonParseOffset -= 3; | ||
return '[[]' + fragment; | ||
} | ||
} | ||
return fragment; | ||
} | ||
flush(chunk, start, end) { | ||
let fragment = chunk.slice(start, end); | ||
this.jsonParseOffset = this.pos; // using for position correction in JSON.parse() error if any | ||
// Save position correction an error in JSON.parse() if any | ||
this.jsonParseOffset = this.chunkOffset + start; | ||
// Prepend pending chunk if any | ||
if (this.pendingChunk !== null) { | ||
fragment = this.pendingChunk + fragment; | ||
this.jsonParseOffset -= this.pendingChunk.length; | ||
this.pendingChunk = null; | ||
} | ||
// Skip a comma at the beginning if any | ||
if (fragment[0] === ',') { | ||
fragment = fragment.slice(1); | ||
this.jsonParseOffset++; | ||
} | ||
if (this.flushDepth === this.lastFlushDepth) { | ||
// Depth didn't changed, so it's a root value or entry/element set | ||
if (this.flushDepth > 0) { | ||
this.jsonParseOffset--; | ||
// Append new entries or elements | ||
if (this.stack[this.flushDepth - 1] === STACK_OBJECT) { | ||
Object.assign(this.valueStack.value, JSON.parse('{' + fragment + '}')); | ||
} else { | ||
append(this.valueStack.value, JSON.parse('[' + fragment + ']')); | ||
} | ||
this.parseAndAppend(this.prepareAddition(fragment), true); | ||
} else { | ||
@@ -155,10 +192,3 @@ // That's an entire value on a top level | ||
} else { | ||
this.jsonParseOffset--; | ||
// Parse fragment and append to current value | ||
if (this.stack[this.lastFlushDepth - 1] === STACK_OBJECT) { | ||
Object.assign(this.valueStack.value, JSON.parse('{' + fragment + '}')); | ||
} else { | ||
append(this.valueStack.value, JSON.parse('[' + fragment + ']')); | ||
} | ||
this.parseAndAppend(this.prepareAddition(fragment), true); | ||
} | ||
@@ -186,3 +216,5 @@ | ||
} | ||
} else { // this.flushDepth < this.lastFlushDepth | ||
} else /* this.flushDepth < this.lastFlushDepth */ { | ||
fragment = this.prepareAddition(fragment); | ||
// Add missed opening brackets/parentheses | ||
@@ -194,7 +226,3 @@ for (let i = this.lastFlushDepth - 1; i >= this.flushDepth; i--) { | ||
if (this.stack[this.lastFlushDepth - 1] === STACK_OBJECT) { | ||
Object.assign(this.valueStack.value, JSON.parse(fragment)); | ||
} else { | ||
append(this.valueStack.value, JSON.parse(fragment)); | ||
} | ||
this.parseAndAppend(fragment, false); | ||
@@ -206,3 +234,2 @@ for (let i = this.lastFlushDepth - 1; i >= this.flushDepth; i--) { | ||
this.pos += end - start; | ||
this.lastFlushDepth = this.flushDepth; | ||
@@ -293,3 +320,3 @@ } | ||
case 0x7B: /* { */ | ||
// begin object | ||
// Open an object | ||
flushPoint = i + 1; | ||
@@ -300,3 +327,3 @@ this.stack[this.flushDepth++] = STACK_OBJECT; | ||
case 0x5B: /* [ */ | ||
// begin array | ||
// Open an array | ||
flushPoint = i + 1; | ||
@@ -308,3 +335,3 @@ this.stack[this.flushDepth++] = STACK_ARRAY; | ||
case 0x7D: /* } */ | ||
// end object or array | ||
// Close an object or array | ||
flushPoint = i + 1; | ||
@@ -319,2 +346,17 @@ this.flushDepth--; | ||
break; | ||
case 0x09: /* \t */ | ||
case 0x0A: /* \n */ | ||
case 0x0D: /* \r */ | ||
case 0x20: /* space */ | ||
// Move points forward when they points on current position and it's a whitespace | ||
if (lastFlushPoint === i) { | ||
lastFlushPoint++; | ||
} | ||
if (flushPoint === i) { | ||
flushPoint++; | ||
} | ||
break; | ||
} | ||
@@ -327,10 +369,16 @@ } | ||
// Produce pendingChunk if any | ||
// Produce pendingChunk if something left | ||
if (flushPoint < chunkLength) { | ||
const newPending = chunk.slice(flushPoint, chunkLength); | ||
if (this.pendingChunk !== null) { | ||
// When there is already a pending chunk then no flush happened, | ||
// appending entire chunk to pending one | ||
this.pendingChunk += chunk; | ||
} else { | ||
// Create a pending chunk, it will start with non-whitespace since | ||
// flushPoint was moved forward away from whitespaces on scan | ||
this.pendingChunk = chunk.slice(flushPoint, chunkLength); | ||
} | ||
} | ||
this.pendingChunk = this.pendingChunk !== null | ||
? this.pendingChunk + newPending | ||
: newPending; | ||
} | ||
this.chunkOffset += chunkLength; | ||
} | ||
@@ -340,6 +388,3 @@ | ||
if (this.pendingChunk !== null) { | ||
if (/[^ \t\r\n]/.test(this.pendingChunk)) { | ||
this.flush('', 0, 0); | ||
} | ||
this.flush('', 0, 0); | ||
this.pendingChunk = null; | ||
@@ -346,0 +391,0 @@ } |
@@ -128,10 +128,10 @@ const { | ||
for (const property in value) { | ||
if (hasOwnProperty.call(value, property)) { | ||
for (const key in value) { | ||
if (hasOwnProperty.call(value, key) && (allowlist === null || allowlist.has(key))) { | ||
const prevLength = length; | ||
walk(value, property, value[property]); | ||
walk(value, key, value[key]); | ||
if (prevLength !== length) { | ||
// value is printed | ||
length += stringLength(property) + 1; // "property": | ||
length += stringLength(key) + 1; // "key": | ||
entries++; | ||
@@ -203,3 +203,10 @@ } | ||
let allowlist = null; | ||
replacer = normalizeReplacer(replacer); | ||
if (Array.isArray(replacer)) { | ||
allowlist = new Set(replacer); | ||
replacer = null; | ||
} | ||
space = spaceLength(space); | ||
@@ -206,0 +213,0 @@ options = options || {}; |
@@ -17,2 +17,3 @@ const { Readable } = require('stream'); | ||
const noop = () => {}; | ||
const hasOwnProperty = Object.prototype.hasOwnProperty; | ||
@@ -158,3 +159,12 @@ // TODO: Remove when drop support for Node.js 10 | ||
this.getKeys = Object.keys; | ||
this.replacer = normalizeReplacer(replacer); | ||
if (Array.isArray(this.replacer)) { | ||
const allowlist = this.replacer; | ||
this.getKeys = (value) => allowlist.filter(key => hasOwnProperty.call(value, key)); | ||
this.replacer = null; | ||
} | ||
this.space = normalizeSpace(space); | ||
@@ -222,3 +232,3 @@ this._depth = 0; | ||
first: false, | ||
keys: Object.keys(value) | ||
keys: this.getKeys(value) | ||
}); | ||
@@ -225,0 +235,0 @@ break; |
@@ -100,10 +100,11 @@ const PrimitiveType = 1; | ||
if (Array.isArray(replacer)) { | ||
const whitelist = new Set(replacer | ||
.map(item => typeof item === 'string' || typeof item === 'number' ? String(item) : null) | ||
const allowlist = new Set(replacer | ||
.map(item => { | ||
const cls = item && item.constructor; | ||
return cls === String || cls === Number ? String(item) : null; | ||
}) | ||
.filter(item => typeof item === 'string') | ||
); | ||
whitelist.add(''); | ||
return (key, value) => whitelist.has(key) ? value : undefined; | ||
return [...allowlist]; | ||
} | ||
@@ -110,0 +111,0 @@ |
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
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
83398
1717
257
11