• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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