1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
***************
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.
|