JsonBuilder

A streaming writer that tracks just enough structure to place commas.

Updated 2 min read

JsonBuilder writes JSON incrementally. It tracks enough state to place commas correctly; balancing begin/end pairs is the caller's job.

var body = new JsonBuilder();
body.beginObject();
body.field("status", "ok");          // string, int, bool and real overloads
body.key("items");
body.beginArray();
body.value("first");
body.endArray();
body.rawValue(alreadySerialised);    // the caller owns validity
body.endObject();
return jsonResponse(body.done());

Why not string concatenation

Two reasons, and both are correctness rather than taste.

Escaping. Strings are escaped on the way in, including U+2028 and U+2029 โ€” literal line terminators that are legal in JSON but break a <script> block when JSON is inlined into a document. <, > and & are escaped as \u003c, \u003e and \u0026 for the same reason.

Numbers. NaN and the infinities are not valid JSON. They serialise as null rather than producing a document that no parser accepts.

A typed result, serialised strictly

Define a record, fill it, then serialise it in one place:

record SearchResult {
  var title: string;
  var path: string;
  var excerpt: string;
  var kind: string;
  var score: int;
}
body.key("results");
body.beginArray();
for hit in hits {
  body.beginObject();
  body.field("title", hit.title);
  body.field("path", hit.path);
  body.field("excerpt", hit.excerpt);
  body.field("kind", hit.kind);
  body.field("score", hit.score);
  body.endObject();
}
body.endArray();

The API route for this wiki's search is that loop and nothing else. The record is the schema, the builder is the encoder, and there is no place where a stray + "\"" + can go wrong.

Island props

Island props are JSON too, and they are attribute text in a document the client can read:

var props = new JsonBuilder();
props.beginObject();
props.field("endpoint", "/api/search");
props.field("limit", 8);
props.endObject();