Skip to content

aowlspt/json

Source: aowl/src/aowlspt/json.nim — 701 lines.

Reading JSON, for mods.

let name = body.field("Info.Nickname").asText let n = body.field("items").count for it in body.field("items").each: echo it.field("_id").asText

A mod on either side spends most of its time reading a document it did not build: a request body, a template out of the database, its own saved state. Without this it would either hand-scan strings or pull in a parser, and the first is what produces the class of bug where a nested "_id" is mistaken for the outer one.

It scans; it does not build a tree. A JsonRef is a pair of offsets into text somebody else owns, so field costs a walk of the document and nothing else — no allocation, no copy, and no lifetime to think about beyond the text it came from. That is the right trade for the access pattern here (a handful of paths out of each body) and the wrong one for walking a 40MB item table field by field, which is what count/each exist to avoid needing.

Paths are dotted, with [n] for array elements:

"Info.Nickname" "items[0]._id" "data[2].Slots[1]"

A key containing a dot is reachable with child, one step at a time. That is deliberate: an escaping rule inside the path syntax would be one more thing to get wrong at a call site that reads fine. Error semantics: field, child, and at return notFound in two cases: (1) a step in the path does not exist, or (2) the path navigation was incorrect (mismatched braces, reached array boundary, etc). The API does not distinguish these two cases. Callers should validate document structure before relying on specific paths; use exists() to check if a reference was found. Reading a notFound ref is safe (returns empty string or default values) and will not crash, but confidently reading a default value as if it were the actual data is a silent failure. This is intentional: distinguishing "absent" from "mis-navigated" would require a three-value result type, making every call site more verbose.

Types

JsonRef

nim
  JsonRef* = object
    text*: string
    first*: int   ## index of the value's first character
    last*: int    ## index of its last character, inclusive
    found*: bool

A view of one JSON value inside text. found distinguishes "the path is not there" from "the path is there and holds null", which is a distinction the game's own bodies rely on.

aowl/src/aowlspt/json.nim:42

Member

nim
  Member* = object
    name*: string
    value*: string   ## the member's value as raw JSON

aowl/src/aowlspt/json.nim:511

Doc

nim
  Doc* = object
    fields*: seq[Member]
    ok*: bool

A JSON object, taken apart for editing. ok is false when the text was not an object -- reported rather than raised, because the text usually came from a request body and a malformed one is an answer to give, not a crash to take.

aowl/src/aowlspt/json.nim:515

List

nim
  List* = object
    items*: seq[string]
    ok*: bool

A JSON array, taken apart for editing. Elements stay as raw text.

aowl/src/aowlspt/json.nim:523

Routines

notFound

nim
func notFound*(): JsonRef

aowl/src/aowlspt/json.nim:51

raw

nim
proc raw*(j: JsonRef): string

The value exactly as it appears, "quotes" and all.

aowl/src/aowlspt/json.nim:164

exists

nim
proc exists*(j: JsonRef): bool

aowl/src/aowlspt/json.nim:170

isNull

nim
proc isNull*(j: JsonRef): bool

aowl/src/aowlspt/json.nim:172

isText

nim
proc isText*(j: JsonRef): bool

aowl/src/aowlspt/json.nim:176

isObject

nim
proc isObject*(j: JsonRef): bool

aowl/src/aowlspt/json.nim:180

isArray

nim
proc isArray*(j: JsonRef): bool

aowl/src/aowlspt/json.nim:184

asText

nim
proc asText*(j: JsonRef; default: string = ""): string

A string value, unescaped. A non-string value comes back as written, so reading a number as text gives "42" rather than the empty string.

aowl/src/aowlspt/json.nim:188

asFloat

nim
proc asFloat*(j: JsonRef; default: float = 0.0): float

aowl/src/aowlspt/json.nim:198

asInt

nim
proc asInt*(j: JsonRef; default: int = 0): int

aowl/src/aowlspt/json.nim:251

asBool

nim
proc asBool*(j: JsonRef; default: bool = false): bool

aowl/src/aowlspt/json.nim:255

child

nim
proc child*(j: JsonRef; key: string): JsonRef

One member of an object, by exact key. The step field is built from, and the way to reach a key that contains a dot.

aowl/src/aowlspt/json.nim:264

count

nim
proc count*(j: JsonRef): int

The number of elements in an array, or of members in an object. Zero for anything else, including a value that is not there.

aowl/src/aowlspt/json.nim:304

at

nim
proc at*(j: JsonRef; index: int): JsonRef

One element of an array. Out of range is notFound, not a crash: an index from a request body is not something a mod should have to bounds-check before it can read it.

aowl/src/aowlspt/json.nim:340

keys

nim
proc keys*(j: JsonRef): seq[string]

The member names of an object, in document order.

aowl/src/aowlspt/json.nim:372

each

nim
proc each*(j: JsonRef): seq[JsonRef]

Every element of an array, as views. Built once so a loop over it is a walk of a sequence rather than a rescan of the document per index -- at in a loop is quadratic and this is the reason not to write that.

aowl/src/aowlspt/json.nim:403

whole

nim
proc whole*(text: string): JsonRef

The document itself, as a value.

aowl/src/aowlspt/json.nim:434

field

nim
proc field*(j: JsonRef; path: string): JsonRef

A dotted path from this value. An empty path is the value itself.

aowl/src/aowlspt/json.nim:474

field

nim
proc field*(text, path: string): JsonRef

A dotted path from a document.

aowl/src/aowlspt/json.nim:493

members

nim
proc members*(j: JsonRef): seq[Member]

aowl/src/aowlspt/json.nim:528

parseObject

nim
proc parseObject*(text: string): Doc

aowl/src/aowlspt/json.nim:565

parseObject

nim
proc parseObject*(j: JsonRef): Doc

aowl/src/aowlspt/json.nim:571

newDoc

nim
proc newDoc*(): Doc

aowl/src/aowlspt/json.nim:576

has

nim
proc has*(d: Doc; name: string): bool

aowl/src/aowlspt/json.nim:578

getRaw

nim
proc getRaw*(d: Doc; name: string): string

aowl/src/aowlspt/json.nim:584

get

nim
proc get*(d: Doc; name: string): JsonRef

aowl/src/aowlspt/json.nim:590

escapeText

nim
proc escapeText*(s: string): string

The JSON string escapes. Present here as well as in aowlspt/server because a client-side mod editing a document must not have to import the server API to do it.

aowl/src/aowlspt/json.nim:596

quoted

nim
proc quoted*(s: string): string

aowl/src/aowlspt/json.nim:617

setRaw

nim
proc setRaw*(d: var Doc; name, rawValue: string)

Sets a member, keeping its position if it is already there. Position matters less than it looks: it keeps a diff of two saved profiles readable, which is the difference between finding a bug and re-reading the whole file.

aowl/src/aowlspt/json.nim:619

setText

nim
proc setText*(d: var Doc; name, value: string)

aowl/src/aowlspt/json.nim:629

setNumber

nim
proc setNumber*(d: var Doc; name: string; value: int)

aowl/src/aowlspt/json.nim:630

setNumber

nim
proc setNumber*(d: var Doc; name: string; value: float)

aowl/src/aowlspt/json.nim:631

setBool

nim
proc setBool*(d: var Doc; name: string; value: bool)

aowl/src/aowlspt/json.nim:632

remove

nim
proc remove*(d: var Doc; name: string)

aowl/src/aowlspt/json.nim:635

text

nim
proc text*(d: Doc): string

aowl/src/aowlspt/json.nim:642

parseArray

nim
proc parseArray*(text: string): List

aowl/src/aowlspt/json.nim:651

parseArray

nim
proc parseArray*(j: JsonRef): List

aowl/src/aowlspt/json.nim:661

newList

nim
proc newList*(): List

aowl/src/aowlspt/json.nim:670

len

nim
proc len*(l: List): int

aowl/src/aowlspt/json.nim:672

at

nim
proc at*(l: List; index: int): JsonRef

aowl/src/aowlspt/json.nim:674

add

nim
proc add*(l: var List; rawValue: string)

aowl/src/aowlspt/json.nim:679

add

nim
proc add*(l: var List; d: Doc)

aowl/src/aowlspt/json.nim:680

replaceAt

nim
proc replaceAt*(l: var List; index: int; rawValue: string)

aowl/src/aowlspt/json.nim:682

removeAt

nim
proc removeAt*(l: var List; index: int)

aowl/src/aowlspt/json.nim:686

text

nim
proc text*(l: List): string

aowl/src/aowlspt/json.nim:695

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.