Data & Hashing
An RFC 8259 JSON parser and serializer. A JsonDoc owns a flat arena of JsonNodes; an array or object
refers to its children by JsonNodeId. Walk a document through the JsonDoc accessors.
| Method | Returns | Throws | Description |
|---|---|---|---|
Json.parse(text String) |
JsonDoc |
JsonError |
Parse a document; doc.root is the top-level value. |
Json.stringify(doc JsonDoc) |
String |
— | Compact output of the tree at doc.root. NaN and infinities are written as null. |
Json.stringifyPrettyNode(doc JsonDoc, root JsonNodeId) |
String |
— | Indented output (two spaces per level) of the subtree at root. |
Json.quote(text String) |
String |
— | text as one quoted JSON string value, with ", \ and control characters escaped. |
JsonDoc
Section titled “JsonDoc”| Member | Returns | Throws | Description |
|---|---|---|---|
JsonDoc.create() |
JsonDoc |
— | An empty document. |
nodes |
field, Array with JsonNode |
— | The arena. |
root |
field, JsonNodeId |
— | The top-level node’s id, set by a parse. For an array or object it is the last node added, because children are added before their parent. Assign it when building. |
add(node JsonNode) |
JsonNodeId |
— | Append a node and return its id. |
get(id JsonNodeId) |
JsonNode |
— | The node; an id outside the arena panics. |
rootKind() |
JsonKind |
— | The kind of the root node. |
getChild(parent JsonNodeId, key String) |
JsonNodeId |
JsonAccessError |
An object member’s id: notObject or missingKey. |
getString(parent, key:) |
String |
JsonAccessError |
A string member; wrongType for another kind. |
getInt(parent, key:) |
int(i64.min to i64.max) |
JsonAccessError |
A number member, truncated toward zero. |
getBool(parent, key:) |
bool |
JsonAccessError |
A boolean member. |
arrayLength(id JsonNodeId) |
int(0 to u64.max) |
JsonAccessError |
notArray for another kind. |
arrayAt(id JsonNodeId, index) |
JsonNodeId |
JsonAccessError |
outOfBounds past the end. |
JsonNode
Section titled “JsonNode”| Member | Description |
|---|---|
kind |
JsonKind |
boolValue |
Set for jsonBool |
numberValue |
Set for jsonNumber (a float) |
stringValue |
Set for jsonString |
children |
JsonNodeIdArray, for jsonArray and jsonObject |
keys |
StringArray, parallel to children, for jsonObject |
JsonNode.nullNode() |
A null |
JsonNode.boolNode(value bool) |
A boolean |
JsonNode.numberNode(value float) |
A number |
JsonNode.stringNode(value String) |
A string |
JsonNode.arrayNode(children JsonNodeIdArray) |
An array of already-added nodes |
JsonNode.objectNode(keys StringArray, children JsonNodeIdArray) |
An object; keys[i] names children[i] |
Kinds and errors
Section titled “Kinds and errors”| Enum | Cases |
|---|---|
JsonKind |
jsonNull, jsonBool, jsonNumber, jsonString, jsonArray, jsonObject |
JsonError (from parse) |
unexpectedChar, unexpectedEof, invalidEscape, invalidNumber, invalidSurrogate, trailingContent |
JsonAccessError (from accessors) |
notObject, notArray, missingKey, wrongType, outOfBounds |
JsonNodeId is int(0 to u64.max) and JsonNodeIdArray is Array with JsonNodeId.
function main() returns ExitCode let doc = try Json.parse("\{\"name\": \"maxon\", \"stars\": 42, \"tags\": [\"a\", \"b\"]\}") otherwise (e) 'bad' print("invalid JSON: {e}\n") return 1 end 'bad'
let name = try doc.getString(doc.root, key: "name") otherwise "?" let stars = try doc.getInt(doc.root, key: "stars") otherwise 0 let tags = try doc.getChild(doc.root, key: "tags") otherwise 0 let second = try doc.arrayAt(tags, index: 1) otherwise 0 print("{doc.rootKind()} {name} {stars} {doc.get(second).stringValue}\n")
var built = JsonDoc.create() var keys = StringArray.create() var children = JsonNodeIdArray.create() keys.push("ok") children.push(built.add(JsonNode.boolNode(true))) keys.push("score") children.push(built.add(JsonNode.numberNode(1.5))) built.root = built.add(JsonNode.objectNode(keys, children: children)) print("{Json.stringify(built)}\n") return 0end 'main'Output: jsonObject maxon 42 b and {"ok":true,"score":1.5}.
Sha256
Section titled “Sha256”| Function | Returns | Description |
|---|---|---|
sha256(data ByteArray) |
ByteArray |
The 32-byte SHA-256 digest (FIPS 180-4). |
function main() returns ExitCode let digest = sha256("abc".toByteArray()) var hex = ""
for b in digest 'each' hex.append("{b:02x}") end 'each'
print("{digest.count()} {hex}\n") return 0end 'main'Output: 32 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad.
Hasher
Section titled “Hasher”Hasher is an incremental FNV-1a 64-bit hash, for content keys and cache digests. It is not cryptographic.
Its result is a HashDigest, bits(64).
| Member | Returns | Description |
|---|---|---|
Hasher.create() |
Hasher |
A hasher at the FNV-1a offset basis. |
Hasher.resume(state HashDigest) |
Hasher |
Continue from a value finalize() returned. |
combine(value HashDigest) |
— | Fold in one value. |
combine(bytes ByteArray) |
— | Fold in each byte. |
finalize() |
HashDigest |
The running state, not post-processed, so resume(finalize()) continues exactly. |
Hasher.empty() |
HashDigest |
The state of an empty fold. |
Hasher.combined(state HashDigest, value HashDigest) |
HashDigest |
One step, as an expression. |
Hasher.combined(state HashDigest, bytes ByteArray) |
HashDigest |
Each byte, as an expression. |
FNV-1a folds a flat byte sequence and records no lengths, so "ab" then "c" hashes the same as "a"
then "bc". When hashing a sequence of parts, combine each part’s length too.
function main() returns ExitCode var hasher = Hasher.create() hasher.combine("ab".toByteArray()) hasher.combine(7)
let direct = Hasher.combined(Hasher.combined(Hasher.empty(), bytes: "ab".toByteArray()), value: 7) print("{hasher.finalize() == direct}\n") return 0end 'main'Output: true.