UNPKG

@xtao-org/jsonhilo

Version:

Pure JavaScript minimal lossless JSON parse event streaming, akin to SAX. Fast, modular, and dependency-free.

176 lines (169 loc) 4.96 kB
import {CodePoint, error} from './JsonLow.js' const {_t_, _n_, _b_, _r_, _f_} = CodePoint /** * * @param {import('./JsonHigh.js').JsonHighHandlers<Feedback, End>} next */ export const JsonLowToHigh = (next) => { const { maxStringBufferLength = Infinity, maxNumberLength = 8192, parseNumbers = true, } = next if (maxStringBufferLength < 1) throw Error(`maxStringBufferLength must be at least 1!`) if (maxNumberLength < 1) throw Error(`maxNumberLength must be at least 1!`) let mode = 'top' let stringKind = 'string' let stringBuffer = '' // note: in codepoints, so counting manually for performance let stringBufferLength = 0 let numberBuffer = '' let hexBuffer = [] const self = { // note: for the closeNumber edge case we store feedback here // and check inside JsonHigh closeNumberFeedback: undefined, openString: () => { stringBuffer = '' stringBufferLength = 0 mode = 'string' stringKind = 'string' return next.openString?.() }, openKey: () => { stringBuffer = '' stringBufferLength = 0 mode = 'string' stringKind = 'key' return next.openKey?.() }, openNumber: (codePoint) => { numberBuffer = String.fromCharCode(codePoint) mode = 'number' return next.openNumber?.() }, openObject: () => { return next.openObject?.() }, openArray: () => { return next.openArray?.() }, closeObject: () => { return next.closeObject?.() }, closeArray: () => { return next.closeArray?.() }, closeTrue: () => { return next.value?.(true) }, closeFalse: () => { return next.value?.(false) }, closeNull: () => { return next.value?.(null) }, codePoint: (codePoint) => { if (mode === 'string') { const c = String.fromCodePoint(codePoint) if (stringBufferLength === maxStringBufferLength) { const buf = stringBuffer stringBuffer = c stringBufferLength = 1 return stringKind === 'string'? next.bufferString?.(buf): next.bufferKey?.(buf) } stringBuffer += c stringBufferLength += 1 } else if (mode === 'escape') { let c if (codePoint === _n_) c = '\n' else if (codePoint === _t_) c = '\t' else if (codePoint === _r_) c = '\r' else if (codePoint === _b_) c = '\b' else if (codePoint === _f_) c = '\f' else { // " or \\ or / c = String.fromCharCode(codePoint) } mode = 'string' if (stringBufferLength === maxStringBufferLength) { const buf = stringBuffer stringBuffer = c stringBufferLength = 1 return stringKind === 'string'? next.bufferString?.(buf): next.bufferKey?.(buf) } stringBuffer += c stringBufferLength += 1 } else if (mode === 'hex') { hexBuffer.push(codePoint) } else if (mode === 'number') { if (numberBuffer.length === maxNumberLength) return error(`Number length over the limit of ${maxNumberLength}! Try increasing the limit.`) numberBuffer += String.fromCharCode(codePoint) } }, escape: () => { mode = 'escape' }, openHex: () => { hexBuffer = [] mode = 'hex' }, closeString: () => { mode = 'top' if (maxStringBufferLength === Infinity) { return next.value?.(stringBuffer) } else { // note: wrapping feedbacks in an object to work around PosInfoAdapter return {feedbacks: [ next.bufferString?.(stringBuffer), next.closeString?.(), ]} } }, closeKey: () => { mode = 'top' if (maxStringBufferLength === Infinity) { return next.key?.(stringBuffer) } else { // note: wrapping feedbacks in an object to work around PosInfoAdapter return {feedbacks: [ next.bufferKey?.(stringBuffer), next.closeKey?.(), ]} } }, closeHex: (codePoint) => { hexBuffer.push(codePoint) mode = 'string' const c = String.fromCharCode(Number.parseInt(String.fromCharCode(...hexBuffer), 16)) if (stringBufferLength === maxStringBufferLength) { const buf = stringBuffer stringBuffer = c stringBufferLength = 1 return stringKind === 'string'? next.bufferString?.(buf): next.bufferKey?.(buf) } stringBuffer += c stringBufferLength += 1 }, closeNumber: () => { mode = 'top' if (parseNumbers) { self.closeNumberFeedback = next.value?.( Number.parseFloat(numberBuffer), ) } else { self.closeNumberFeedback = next.bufferNumber?.(numberBuffer) } }, end: () => { return next.end?.() }, } return self }