1// put javascript in here 2'use strict' 3 4const Parser = require('jsonparse') 5const Minipass = require('minipass') 6 7class JSONStreamError extends Error { 8 constructor (err, caller) { 9 super(err.message) 10 Error.captureStackTrace(this, caller || this.constructor) 11 } 12 get name () { 13 return 'JSONStreamError' 14 } 15 set name (n) {} 16} 17 18const check = (x, y) => 19 typeof x === 'string' ? String(y) === x 20 : x && typeof x.test === 'function' ? x.test(y) 21 : typeof x === 'boolean' || typeof x === 'object' ? x 22 : typeof x === 'function' ? x(y) 23 : false 24 25const _parser = Symbol('_parser') 26const _onValue = Symbol('_onValue') 27const _onTokenOriginal = Symbol('_onTokenOriginal') 28const _onToken = Symbol('_onToken') 29const _onError = Symbol('_onError') 30const _count = Symbol('_count') 31const _path = Symbol('_path') 32const _map = Symbol('_map') 33const _root = Symbol('_root') 34const _header = Symbol('_header') 35const _footer = Symbol('_footer') 36const _setHeaderFooter = Symbol('_setHeaderFooter') 37const _ending = Symbol('_ending') 38 39class JSONStream extends Minipass { 40 constructor (opts = {}) { 41 super({ 42 ...opts, 43 objectMode: true, 44 }) 45 46 this[_ending] = false 47 const parser = this[_parser] = new Parser() 48 parser.onValue = value => this[_onValue](value) 49 this[_onTokenOriginal] = parser.onToken 50 parser.onToken = (token, value) => this[_onToken](token, value) 51 parser.onError = er => this[_onError](er) 52 53 this[_count] = 0 54 this[_path] = typeof opts.path === 'string' 55 ? opts.path.split('.').map(e => 56 e === '$*' ? { emitKey: true } 57 : e === '*' ? true 58 : e === '' ? { recurse: true } 59 : e) 60 : Array.isArray(opts.path) && opts.path.length ? opts.path 61 : null 62 63 this[_map] = typeof opts.map === 'function' ? opts.map : null 64 this[_root] = null 65 this[_header] = null 66 this[_footer] = null 67 this[_count] = 0 68 } 69 70 [_setHeaderFooter] (key, value) { 71 // header has not been emitted yet 72 if (this[_header] !== false) { 73 this[_header] = this[_header] || {} 74 this[_header][key] = value 75 } 76 77 // footer has not been emitted yet but header has 78 if (this[_footer] !== false && this[_header] === false) { 79 this[_footer] = this[_footer] || {} 80 this[_footer][key] = value 81 } 82 } 83 84 [_onError] (er) { 85 // error will always happen during a write() call. 86 const caller = this[_ending] ? this.end : this.write 87 this[_ending] = false 88 return this.emit('error', new JSONStreamError(er, caller)) 89 } 90 91 [_onToken] (token, value) { 92 const parser = this[_parser] 93 this[_onTokenOriginal].call(parser, token, value) 94 if (parser.stack.length === 0) { 95 if (this[_root]) { 96 const root = this[_root] 97 if (!this[_path]) 98 super.write(root) 99 this[_root] = null 100 this[_count] = 0 101 } 102 } 103 } 104 105 [_onValue] (value) { 106 const parser = this[_parser] 107 // the LAST onValue encountered is the root object. 108 // just overwrite it each time. 109 this[_root] = value 110 111 if(!this[_path]) return 112 113 let i = 0 // iterates on path 114 let j = 0 // iterates on stack 115 let emitKey = false 116 let emitPath = false 117 while (i < this[_path].length) { 118 const key = this[_path][i] 119 j++ 120 121 if (key && !key.recurse) { 122 const c = (j === parser.stack.length) ? parser : parser.stack[j] 123 if (!c) return 124 if (!check(key, c.key)) { 125 this[_setHeaderFooter](c.key, value) 126 return 127 } 128 emitKey = !!key.emitKey; 129 emitPath = !!key.emitPath; 130 i++ 131 } else { 132 i++ 133 if (i >= this[_path].length) 134 return 135 const nextKey = this[_path][i] 136 if (!nextKey) 137 return 138 while (true) { 139 const c = (j === parser.stack.length) ? parser : parser.stack[j] 140 if (!c) return 141 if (check(nextKey, c.key)) { 142 i++ 143 if (!Object.isFrozen(parser.stack[j])) 144 parser.stack[j].value = null 145 break 146 } else { 147 this[_setHeaderFooter](c.key, value) 148 } 149 j++ 150 } 151 } 152 } 153 154 // emit header 155 if (this[_header]) { 156 const header = this[_header] 157 this[_header] = false 158 this.emit('header', header) 159 } 160 if (j !== parser.stack.length) return 161 162 this[_count] ++ 163 const actualPath = parser.stack.slice(1) 164 .map(e => e.key).concat([parser.key]) 165 if (value !== null && value !== undefined) { 166 const data = this[_map] ? this[_map](value, actualPath) : value 167 if (data !== null && data !== undefined) { 168 const emit = emitKey || emitPath ? { value: data } : data 169 if (emitKey) 170 emit.key = parser.key 171 if (emitPath) 172 emit.path = actualPath 173 super.write(emit) 174 } 175 } 176 177 if (parser.value) 178 delete parser.value[parser.key] 179 180 for (const k of parser.stack) { 181 k.value = null 182 } 183 } 184 185 write (chunk, encoding, cb) { 186 if (typeof encoding === 'function') 187 cb = encoding, encoding = null 188 if (typeof chunk === 'string') 189 chunk = Buffer.from(chunk, encoding) 190 else if (!Buffer.isBuffer(chunk)) 191 return this.emit('error', new TypeError( 192 'Can only parse JSON from string or buffer input')) 193 this[_parser].write(chunk) 194 if (cb) 195 cb() 196 return this.flowing 197 } 198 199 end (chunk, encoding, cb) { 200 this[_ending] = true 201 if (typeof encoding === 'function') 202 cb = encoding, encoding = null 203 if (typeof chunk === 'function') 204 cb = chunk, chunk = null 205 if (chunk) 206 this.write(chunk, encoding) 207 if (cb) 208 this.once('end', cb) 209 210 const h = this[_header] 211 this[_header] = null 212 const f = this[_footer] 213 this[_footer] = null 214 if (h) 215 this.emit('header', h) 216 if (f) 217 this.emit('footer', f) 218 return super.end() 219 } 220 221 static get JSONStreamError () { return JSONStreamError } 222 static parse (path, map) { 223 return new JSONStream({path, map}) 224 } 225} 226 227module.exports = JSONStream 228