This website is meant to be read and understood quickly by humans, but is only fully parsable, on a technical level, with the aid of an AI system. Read why →
Loop MMT
Stream a JSON Array into a JSONL Record Streamsource← all gifts

json-source

json-source reads a JSON file (a FILE arg, or stdin) whose top-level value is an ARRAY and emits one element per line (JSONL) — the front of a pipe you feed into the fold/filter/transform gifts (json-source users.json | dedup-filter --key id). Object elements are records the consuming gifts read directly; scalar elements are a stream of scalars. Each element is re-serialized to canonical compact JSON (key order preserved). Zero dependencies, runs unchanged in Node or a browser, same input yields byte-identical output every run.

The honest edge
json-source streams the elements of a TOP-LEVEL JSON ARRAY only — a non-array top-level (an object, a number, a string, a boolean, null) or malformed JSON is refused (exit 2) rather than guess how to streamify a single value. Each element is re-serialized to canonical compact JSON, so number tokens and whitespace are normalized (1e3 -> 1000, 1.0 -> 1) though key order is preserved — the JSON value, not the input bytes. It reads JSON, not JSON5/NDJSON, and parses the WHOLE document (not an incremental stream).
Run it
printf '[{"id":1},{"id":2}]' | node json-source.js # -> {"id":1}\n{"id":2} test_json-source.js (33/33, independent map-route oracle) + Plumb conformance GREEN (21/21, signed 2026-09-12, clock-independent, mutation-bite non-vacuous) Node / browser, no dependencies
The code — every file that ships
json-source.js171 lineson GitHub →
#!/usr/bin/env node
/* json-source.js — stream the elements of a JSON array as a JSONL record stream.
   Dependency-free, deterministic, pure. Runs in Node or a browser. MIT.

   WHAT IT IS. A SOURCE: it turns a JSON array — the shape an API response, an export,
   or a `[ ... ]` file so often arrives in — into the front of a pipe: one element per
   line (JSONL), ready to feed INTO the fold/filter/transform gifts. Give it a file (or
   pipe text on stdin) and it emits each top-level array element as one compact JSON line:

       [{"id":1},{"id":2}]        ->  {"id":1}
                                      {"id":2}
       [1, 2, 3]                  ->  1
                                      2
                                      3

   When the elements are objects, they are records the consuming gifts read directly:
   `json-source users.json | dedup-filter --key id`. When they are scalars, you get a
   stream of scalars — still valid JSONL.

   TOP-LEVEL ARRAY ONLY (the honesty axis). The input's top-level value MUST be a JSON
   array — that is the only thing that IS a stream. A bare object, a number, a string, a
   boolean, or null is a single value, not a stream, so json-source REFUSES it (exit 2)
   rather than guess how to "streamify" it (wrap it? emit its entries? emit it as one
   line?). Each guess is a different tool; refusing keeps json-source one honest thing.
   Malformed JSON is likewise a hard error (exit 2), never a partial or repaired parse.

   CANONICAL RE-SERIALIZATION (declared, so it is pinnable). Each element is emitted via
   canonical compact JSON.stringify — object key order is preserved from the input, but
   input WHITESPACE and NUMBER TOKENS are normalized to canonical JSON form (`1e3` -> 1000,
   `1.0` -> 1, `[ 1 ,2 ]` -> [1,2]). This is the JSON *value*, losslessly; it is not the
   input's exact bytes. The result is deterministic: the same input yields byte-identical
   output every run and every machine.

   THE MODEL
     [FILE]   The JSON file to stream. If omitted, read stdin. Its top-level value must
              be an array; each element becomes one output line.

   DETERMINISM. parse(text) is a pure function — no clock, no randomness, no network — so
   the same text yields byte-identical output every run.

   USAGE
     node json-source.js data.json
     cat data.json | node json-source.js
     node json-source.js --help

   Exit codes: 0 success (including an empty stream from `[]`) · 2 input error (malformed
   JSON, a top-level value that is not an array, an unknown option, a second positional
   file, or an unreadable file). Always a clean one-line message on stderr, never a stack
   trace.

   Released under MIT. Its edge is printed in the README: json-source streams the elements
   of a TOP-LEVEL JSON ARRAY only — it refuses a non-array top-level and malformed JSON
   (exit 2), and it re-serializes each element to canonical compact JSON (normalizing
   number tokens and whitespace, preserving key order). It reads JSON, not JSON5/NDJSON,
   and does not stream incrementally (it parses the whole document).
*/
"use strict";

/* ---- the pure core ------------------------------------------------ */

// Parse JSON text and return its top-level array of elements. Throws a clean Error on
// malformed JSON or a non-array top-level — the CLI turns that into exit 2. Pure.
function parse(text) {
  if (typeof text !== "string") throw new Error("input must be text");
  var value;
  try {
    value = JSON.parse(text);
  } catch (e) {
    throw new Error("input is not valid JSON: " + e.message);
  }
  if (!Array.isArray(value)) {
    var kind = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
    throw new Error("top-level JSON must be an array (got " + kind + ") — a non-array value is not a stream");
  }
  return value;
}

// Render elements as JSONL text (one canonical compact JSON value per line, trailing
// newline if any).
function toJSONL(elements) {
  var s = "";
  for (var i = 0; i < elements.length; i++) s += JSON.stringify(elements[i]) + "\n";
  return s;
}

/* ---- exports (browser + Node) ------------------------------------ */
if (typeof window !== "undefined") {
  window.ForestGifts = window.ForestGifts || {};
  window.ForestGifts.jsonSource = { parse: parse, toJSONL: toJSONL };
}
if (typeof module !== "undefined" && module.exports) {
  module.exports = { parse: parse, toJSONL: toJSONL };
}

/* ---- CLI (runs only when invoked directly, never on require) ------ */

function parseArgs(args) {
  var file;
  var i = 0;
  while (i < args.length) {
    var a = args[i];
    if (a.charAt(0) === "-" && a !== "-") {
      throw new Error("unknown option " + a);
    } else {
      if (file !== undefined) throw new Error("only one input file may be given (got a second: " + JSON.stringify(a) + ")");
      file = a;
      i += 1;
    }
  }
  return { file: file };
}

function readAll(stream) {
  return new Promise(function (resolve, reject) {
    var chunks = [];
    stream.on("data", function (c) { chunks.push(c); });
    stream.on("end", function () { resolve(Buffer.concat(chunks).toString("utf8")); });
    stream.on("error", reject);
  });
}

function helpText() {
  return (
    "json-source.js — stream the elements of a JSON array as a JSONL record stream.\n\n" +
    "  node json-source.js data.json\n" +
    "  cat data.json | node json-source.js\n" +
    "  node json-source.js --help\n\n" +
    "  [FILE]   JSON file to stream (default: read stdin). Top-level must be an array.\n\n" +
    "Emits one canonical compact JSON line per top-level array element.\n\n" +
    "Edge: streams a TOP-LEVEL JSON ARRAY only — a non-array top-level or malformed JSON\n" +
    "is refused (exit 2). Each element is re-serialized to canonical compact JSON (number\n" +
    "tokens and whitespace normalized, key order preserved). Reads JSON, not JSON5/NDJSON,\n" +
    "and parses the whole document (not an incremental stream).\n"
  );
}

function main(argv) {
  var args = argv.slice(2);
  if (args.indexOf("--help") !== -1 || args.indexOf("-h") !== -1) {
    process.stdout.write(helpText());
    return Promise.resolve(0);
  }
  var parsed;
  try { parsed = parseArgs(args); }
  catch (e) { process.stderr.write("json-source: " + e.message + "\n"); return Promise.resolve(2); }

  var getText;
  if (parsed.file !== undefined) {
    getText = new Promise(function (resolve, reject) {
      require("fs").readFile(parsed.file, "utf8", function (err, data) {
        if (err) reject(new Error("cannot read " + JSON.stringify(parsed.file) + ": " + err.code));
        else resolve(data);
      });
    });
  } else {
    getText = readAll(process.stdin);
  }

  return getText.then(function (text) {
    var elements = parse(text);   // throws -> caught below
    process.stdout.write(toJSONL(elements));
    return 0;
  }).catch(function (e) {
    process.stderr.write("json-source: " + e.message + "\n");
    return 2;
  });
}

if (typeof require !== "undefined" && require.main === module) {
  main(process.argv).then(function (code) { process.exitCode = code; });
}
test_json-source.js108 lineson GitHub →
#!/usr/bin/env node
/* test_json-source.js — battery for the json-source gift.
   node test_json-source.js  ->  exit 0 PASS / non-zero FAIL. Zero dependencies.

   Cross-checks the gift against an INDEPENDENT map-route oracle (a different route than
   the gift's loop), frozen hand goldens, the canonical-re-serialization contract,
   determinism, and every fail-closed edge. */
"use strict";
var js = require("./json-source.js");

var pass = 0, fail = 0;
function ok(name, cond) { if (cond) pass++; else { fail++; console.log("  FAIL  " + name); } }
function eqS(name, a, b) {
  if (a === b) pass++;
  else { fail++; console.log("  FAIL  " + name + "\n    exp " + JSON.stringify(b) + "\n    got " + JSON.stringify(a)); }
}
function throws(name, fn) {
  var threw = false; try { fn(); } catch (e) { threw = true; }
  if (threw) pass++; else { fail++; console.log("  FAIL  " + name + " (expected a throw)"); }
}

/* ---- independent oracle: Array.prototype.map route (different route than the gift) ---- */
function oracleJSONL(text) {
  var v = JSON.parse(text);                 // may throw -> caller counts as error
  if (!Array.isArray(v)) throw new Error("not array");
  return v.map(function (el) { return JSON.stringify(el); }).join("") + (v.length ? "" : "");
}
// build expected JSONL the oracle way (join with \n + trailing \n if non-empty)
function oracle(text) {
  var v = JSON.parse(text);
  if (!Array.isArray(v)) throw new Error("not array");
  var out = "";
  v.forEach(function (el) { out += JSON.stringify(el) + "\n"; });
  return out;
}

/* ---- 1. differential grid: gift == oracle on every good vector ---- */
var good = [
  "[]",
  "[1,2,3]",
  '[{"id":1},{"id":2}]',
  '[{"a":1,"b":2},{"a":3,"b":4}]',
  '["x","y","z"]',
  "[true,false,null]",
  '[{"nested":{"k":[1,2]}}]',
  "[ 1 , 2 ,\n 3 ]",                 // whitespace normalized
  "[1e3, 1.0, 100]",                 // number tokens normalized to value
  '[{"b":2,"a":1}]',                 // key order preserved from input
  '[""]',                             // array with one empty string
];
for (var i = 0; i < good.length; i++) {
  eqS("differential[" + i + "] gift==oracle", js.toJSONL(js.parse(good[i])), oracle(good[i]));
}

/* ---- 2. hand goldens (frozen) ---- */
eqS("golden: empty array -> empty stream", js.toJSONL(js.parse("[]")), "");
eqS("golden: scalars", js.toJSONL(js.parse("[1,2,3]")), "1\n2\n3\n");
eqS("golden: objects", js.toJSONL(js.parse('[{"id":1},{"id":2}]')), '{"id":1}\n{"id":2}\n');
eqS("golden: whitespace normalized", js.toJSONL(js.parse("[ 1 , 2 ]")), "1\n2\n");
eqS("golden: number token 1e3 -> 1000", js.toJSONL(js.parse("[1e3]")), "1000\n");
eqS("golden: number token 1.0 -> 1", js.toJSONL(js.parse("[1.0]")), "1\n");
eqS("golden: key order preserved", js.toJSONL(js.parse('[{"b":2,"a":1}]')), '{"b":2,"a":1}\n');
eqS("golden: nested value verbatim-as-value", js.toJSONL(js.parse('[{"k":{"z":[1,2]}}]')), '{"k":{"z":[1,2]}}\n');

/* ---- 3. one line per element ---- */
var recs = js.parse('[{"a":1},{"a":2},{"a":3}]');
ok("count: 3 elements", recs.length === 3);
ok("lines: JSONL has 3 lines", js.toJSONL(recs).split("\n").filter(Boolean).length === 3);

/* ---- 4. fail-closed (exit-2 class) ---- */
throws("bad: malformed JSON", function () { js.parse("[1,2,"); });
throws("bad: top-level object", function () { js.parse('{"a":1}'); });
throws("bad: top-level scalar number", function () { js.parse("42"); });
throws("bad: top-level string", function () { js.parse('"hello"'); });
throws("bad: top-level bool", function () { js.parse("true"); });
throws("bad: top-level null", function () { js.parse("null"); });
throws("bad: empty input (not JSON)", function () { js.parse(""); });
throws("bad: non-string input", function () { js.parse(123); });

/* ---- 5. determinism: parse twice -> byte-identical JSONL ---- */
var arr = [];
for (var k = 0; k < 50; k++) arr.push({ n: k, s: "v" + k });
var big = JSON.stringify(arr);
ok("determinism: two parses byte-identical", js.toJSONL(js.parse(big)) === js.toJSONL(js.parse(big)));

/* ---- 6. toJSONL shape ---- */
ok("toJSONL: one value per line + trailing nl", js.toJSONL([{ a: 1 }]) === '{"a":1}\n');
ok("toJSONL: empty -> empty string", js.toJSONL([]) === "");

/* ---- 7. mutation tripwire: an object-flattening mutant must diverge ---- */
(function () {
  // mutant: emits only the FIRST value of each object (a lossy "flatten"); must diverge
  function mutant(text) {
    var v = JSON.parse(text); var out = "";
    v.forEach(function (el) {
      if (el && typeof el === "object" && !Array.isArray(el)) {
        var ks = Object.keys(el); out += JSON.stringify(ks.length ? el[ks[0]] : el) + "\n";
      } else out += JSON.stringify(el) + "\n";
    });
    return out;
  }
  var v = '[{"a":1,"b":2}]';
  ok("mutation: first-value-flatten is CAUGHT (diverges from gift)",
     mutant(v) !== js.toJSONL(js.parse(v)));
})();

console.log((fail === 0 ? "PASS" : "FAIL") + "  " + pass + "/" + (pass + fail));
process.exit(fail === 0 ? 0 : 1);
Take the whole folder → MIT Node / browser, no dependencies