[6a3a178] | 1 | // put javascript in here
|
---|
| 2 | 'use strict'
|
---|
| 3 |
|
---|
| 4 | const Parser = require('jsonparse')
|
---|
| 5 | const Minipass = require('minipass')
|
---|
| 6 |
|
---|
| 7 | class 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 |
|
---|
| 18 | const 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 |
|
---|
| 25 | const _parser = Symbol('_parser')
|
---|
| 26 | const _onValue = Symbol('_onValue')
|
---|
| 27 | const _onTokenOriginal = Symbol('_onTokenOriginal')
|
---|
| 28 | const _onToken = Symbol('_onToken')
|
---|
| 29 | const _onError = Symbol('_onError')
|
---|
| 30 | const _count = Symbol('_count')
|
---|
| 31 | const _path = Symbol('_path')
|
---|
| 32 | const _map = Symbol('_map')
|
---|
| 33 | const _root = Symbol('_root')
|
---|
| 34 | const _header = Symbol('_header')
|
---|
| 35 | const _footer = Symbol('_footer')
|
---|
| 36 | const _setHeaderFooter = Symbol('_setHeaderFooter')
|
---|
| 37 | const _ending = Symbol('_ending')
|
---|
| 38 |
|
---|
| 39 | class 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 |
|
---|
| 227 | module.exports = JSONStream
|
---|