JSON Writer

Argentum got a JSON module. Its Writer class allows to directly encode in JSON arbitrary application data without creating any intermediate DOM structures.

using sys { log }
using json { Writer }

w = Writer;
w.num(3.14);
log(w.toStr());

// or just
log(Writer.num(3.14).toStr());

// Both variants print 3.14

Writer has a number of methods that write structural and primitive data nodes.
After data is written, a call to toStr() returns the whole created JSON as a string.
For convenience all Writer methods return this, mostly to let us call toStr() method in the end.

Writing primitive data

  • Null node: writer.null()
  • Bool node: writer.bool(true)
  • Numeric node: writer.num(123.4e-10)
  • String node: writer.str("Random text")

Numeric nodes represent 52-bits integers as exact values and use exponent notation where possible. Please notice that JSON numbers are always doubles by standard. If you need to to store anything exceeding 52 bits, use strings.
Strings are represented with utf8 runes for all characters 0x20..0x1ffff and escapes for characters 0x1..0x1f.

log(Writer.str("trn\
   "Hello"
   \there/
").toStr())

//This code prints "\t\"Hello\"\r\n\t\\there\/\r\n"

Argentum's multi-line string with "trn\" formatter prepends each line with a tab, ends it with CR LF and adds the line ending to the last line, as described here: multiple string literals.

Writing arrays

Method arr expects a lambda-parameter that should write the whole array content. This lambda receives one parameter - a reference to the Writer that should be used to write array items. In the following example we pass lambda as {}-block and access its parameter using the default "_"-name:

log(Writer.arr{
   _.null();
   _.num(42);
   _.num(11);
   _.str("Hi");
   _.bool(false)
}.toStr())

// This example prints: [null,42,11,"Hi",false]

The only the Writer that passed inside the arr method is capable of writing multiple JSON nodes, in contrast the writer for root element and writer for field data ignore all calls beyond the first one.

Nested arrays

The arr method can be called from the lambda of another arr call to create arrays inside another arrays:

sys_log(Writer.arr{
    _.num(1.1);
    _.arr{
        _.bool(true);
        _.bool(true);
    };
    _.arr{
        _.bool(false);
        _.bool(false);
    };
    _.num(1.2);
}.toStr());

// This example prints [1.1,[true,true],[false,false],1.2]

Or using this-chaining:

sys_log(Writer.arr{_
    .num(1.1)
    .arr{_.bool(true).bool(true)}
    .arr{_.bool(false).bool(false)}
    .num(1.2)
}.toStr());

Objects

An obj method writes objects. It expects you to provide an object-writer-lambda.
Your object-writer-lambda receives a parameter - a field-writer-lambda that could be called multiple times with a field name string parameter.
Each call to the field-writer lambda returns a Writer that could be used to create a field value.
Example:

sys_log(Writer.obj {
    _("year").num(1972.0);
    _("name").str("Andrey");
    _("details").obj {         // nested object
        _("awake").bool(true);
        _("excels at").arr{_}; // empty array
    };
    _("address").null();
}.toStr());

// Prints {"year":1972,"name":"Andrey","details":{"awake":true,"excels at":[]},"address":null}

This example also demonstrates nested objects and empty arrays.

If you didn't write field value between calls to field writer lambda, then Writer automatically made this field null.

Pretty-printing

By default JSON Writer produces compact JSONs, but this default can be overridden:

Call useTabs() or useSpaces(count) methods to make Writer to format its output with extra spaces and indentations:

sys_log(Writer.useSpaces(2).obj {
    _("year").num(1972.0);
    _("name").str("Andrey");
    _("details").obj {
        _("awake").bool(true);
        _("excels at").arr{_};
    };
    _("address").null();
}.toStr());

// It prints:
// {
//   "year": 1972,
//   "name": "Andrey",
//   "details": {
//     "awake": true,
//     "excels at": []
//   },
//   "address": null
// }

Real world example

Let's assume, that we have these classes:

class Point{
    x = 0f;
    y = 0f;
} 
class Polygon {
    name = "";
    points = Array(Point);
    isActive = false;
}

This function writes an array of Polygons to JSON:

fn polygonsToJson(data Array(Polygon)) str {
    Writer.useTabs().arr {
        data.each `poly _.obj{
            _("name").str(poly.name);
            _("active").bool(poly.isActive);
            _("points").arr\poly.points.each `pt _.obj {
                _("x").num(double(pt.x));
                _("y").num(double(pt.y))
            }
        }
    }.toStr()
}
log(polygonsToJson(myPolygonArray));

// Depending on the content of the myPolygonArray
// this example could print:
// [
//    {
//       "name": "Corner",
//       "active": false,
//       "points": [
//          {
//             "x": 10,
//             "y": -100.01
//          },
//          {
//             "x": 0,
//             "y": 0
//          },
//          {
//             "x": -42,
//             "y": -11
//          }
//       ]
//    },
//    {
//       "name": "A dummy one",
//       "active": true,
//       "points": []
//    }
// ]

As with JSON Parser, this Writer entirely skips the creation of the intermediate JSON DOM structures, thus reducing memory usage by twofold and CPU overhead by threefold.

Readiness

This JSON module with its parser and Writer can be used in Argentum built from sources, it is not yet integrated into playground and binary demo.

Leave a Reply

Your email address will not be published. Required fields are marked *