*************** 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.