All files / utils json-lines-parser.js

56.36% Statements 62/110
100% Branches 0/0
0% Functions 0/5
56.36% Lines 62/110

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 1112x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x                     2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x                                       2x 2x 2x 2x 2x 2x 2x                                        
/**
 * Parses JSONL from a string or iterables of strings.
 * @overload
 * @param {string | Iterable<string>} input - JSONL source.
 * @returns {Generator<*>} generator that yields a parsed json line
 * @example
 * for (const row of parseCSV('{"name": "John","age": 30}\n{"name": "Jane","age": 25}')) {
 *   console.log(row);
 * }
 *
 * @example
 * const chunkArray = ['{"name": "John","age": 30}\n', '{"name": "Jane","age": 25}'];
 * for (const row of parseCSV(chunkArray)) {
 *   console.log(row);
 * }
 */
/**
 * Parses JSONL from async string iterables.
 *
 * @example
 * async function* chunks() {
 *   yield '{"name": "John","age": 30}\n{"na';
 *   yield 'me": "Jane","age": 25}\n';
 * }
 * for await (const row of parseCSV(chunks())) {
 *   console.log(row);
 * }
 * @overload
 * @param {AsyncIterable<string>} input - JSONL source.
 * @returns {AsyncGenerator<*>} generator that yields a parsed json line
 */
/**
 * @param {string | Iterable<string> | AsyncIterable<string>} input - JSONL source.
 * @returns {Generator<*> | AsyncGenerator<*>} generator that yields a parsed json line
 */
export function parseJsonLines (input) {
  if (typeof input === 'string') {
    return parseJsonLinesSync([input])
  } else if (isAsyncIterable(input)) {
    return parseJsonLinesAsync(input)
  } else if (isIterable(input)) {
    return parseJsonLinesSync(input)
  } else {
    throw new TypeError('Input must be a string, Iterable, or AsyncIterable')
  }
}
 
/**
 * @param {*} object - target object
 * @returns {object is Iterable<*>} - true if iterable, false otherwise
 */
const isIterable = (object) => typeof object?.[Symbol.iterator] === 'function'
 
/**
 * @param {*} object - target object
 * @returns {object is AsyncIterable<*>} - true if iterable, false otherwise
 */
const isAsyncIterable = (object) => typeof object?.[Symbol.asyncIterator] === 'function'
 
/**
 * Parses CSV from a synchronous iterable of strings.
 *
 * @param {Iterable<string>} iterable - Iterable of string chunks.
 * @yields {object | null | any[] | number | string} parsed CSV row
 */
function * parseJsonLinesSync (iterable) {
  let buffer = ''

  for (const chunk of iterable) {
    buffer += chunk.toString()

    const lines = buffer.split(/\r?\n/)
    buffer = lines.pop() ?? ''// keep last partial line

    for (const line of lines) {
      if (!line.trim()) { continue }
      yield JSON.parse(line)
    }
  }

  if (buffer.trim()) {
    yield JSON.parse(buffer)
  }
}
 
/**
 * Parses JSON lines from an async iterable of string chunks.
 *
 * @param {AsyncIterable<string>} asyncIterable - Async iterable of chunks.
 * @yields {object | null | any[] | number | string} parsed JSON line
 */
async function * parseJsonLinesAsync (asyncIterable) {
  let buffer = ''

  for await (const chunk of asyncIterable) {
    buffer += chunk.toString()

    const lines = buffer.split(/\r?\n/)
    buffer = lines.pop() ?? ''// keep last partial line

    for (const line of lines) {
      if (!line.trim()) { continue }
      yield JSON.parse(line)
    }
  }

  if (buffer.trim()) {
    yield JSON.parse(buffer)
  }
}