***************
JSON Serializer
***************
Code generators include a JSON serializer which will convert a target
language's representation of Stone data types into JSON. This document explores
how Stone data types, regardless of language, are mapped to JSON.
Primitive Types
===============
========================== ====================================================
Stone Primitive JSON Representation
========================== ====================================================
Boolean Boolean
Bytes String: Base64-encoded
Float{32,64} Number
Int{32,64}, UInt{32,64} Number
List Array
String String
Timestamp String: Encoded using strftime() based on the
Timestamp's format argument.
Void Null
========================== ====================================================
Struct
======
A struct is represented as a JSON object. Each specified field has a key in the
object. For example::
struct Coordinate
x Int64
y Int64
converts to::
{
"x": 1,
"y": 2
}
If an optional (has a default or is nullable) field is not specified, the key
should be omitted. For example, given the following spec::
struct SurveyAnswer
age Int64
name String = "John Doe"
address String?
If ``name`` and ``address`` are unset and ``age`` is 28, then the struct
serializes to::
{
"age": 28
}
Setting ``name`` or ``address`` to ``null`` is not a valid serialization;
deserializers will raise an error.
An explicit ``null`` is allowed for fields with nullable types. While it's
less compact, this makes serialization easier in some languages. The previous
example could therefore be represented as::
{
"age": 28,
"address": null
}
Enumerated Subtypes
-------------------
A struct that enumerates subtypes serializes similarly to a regular struct,
but includes a ``.tag`` key to distinguish the type. Here's an example to
demonstrate::
struct A
union*
b B
c C
w Int64
struct B extends A
x Int64
struct C extends A
y Int64
Serializing ``A`` when it contains a struct ``B`` (with values of ``1`` for
each field) appears as::
{
".tag": "b",
"w": 1,
"x": 1
}
If the recipient receives a tag it cannot match to a type, it should fallback
to the parent type if it's specified as a catch-all.
For example::
{
".tag": "d",
"w": 1,
"z": 1
}
Because ``d`` is unknown, the recipient checks that struct ``A`` is a
catch-all. Since it is, it deserializes the message to an ``A`` object.
Union
=====
Similar to an enumerated subtype struct, recipients should check the ``.tag``
key to determine the union variant.
Let's use the following example to illustrate how a union is serialized based
on the selected variant::
union U
singularity
number Int64
coord Coordinate?
infinity Infinity
struct Coordinate
x Int64
y Int64
union Infinity
positive
negative
The serialization of ``U`` with tag ``singularity`` is::
{
".tag": "singularity"
}
For a union member of primitive type (``number`` in the example), the
serialization is as follows::
{
".tag": "number",
"number": 42
}
Note that ``number`` is used as the value for ``.tag`` and as a key to hold
the value. This same pattern is used for union members with types that are
other unions or structs with enumerated subtypes.
Union members that are ordinary structs (``coord`` in the example) serialize
as the struct with the addition of a ``.tag`` key. For example, the
serialization of ``Coordinate`` is::
{
"x": 1,
"y": 2
}
The serialization of ``U`` with tag ``coord`` is::
{
".tag": "coord",
"x": 1,
"y": 2
}
The serialization of ``U`` with tag ``infinity`` is nested::
{
".tag": "infinity",
"infinity": {
".tag": "positive"
}
}
The same rule applies for members that are enumerated subtypes.
Nullable
--------
Note that ``coord`` references a nullable type. If it's unset, then the
serialization only includes the tag::
{
".tag": "coord"
}
You may notice that if ``Coordinate`` was defined to have no fields, it is
impossible to differentiate between an unset value and a value of coordinate.
In these cases, we prescribe that the deserializer should return a null
or unset value.
Compact Form
------------
Deserializers should support an additional representation of void union
members: the tag itself as a string. For example, tag ``singularity`` could
be serialized as simply::
"singularity"
This is convenient for humans manually entering the argument, allowing them to
avoid typing an extra layer of JSON object nesting.