Docs · Bags & JSON

Bags & JSON

A bag is the script's own record — the closest thing the language has to a class: named fields you put in and read back by name. A list<bag> is a table you can filter, sort and sum. A bag saves itself as JSON across runs, and JSON from anywhere else reads straight into one. This page is the story; the bags and json command cards are in the reference below.

Records — one bag, named fields

newBag() makes an empty one. bagPut(b, "hp", 30) gives it a field — numbers, texts, flags, tiles, nested bags, and lists of any of those. Reads are typed, and each comes in two spellings:

  • bagNumber(b, "hp")int?none when the field is missing or holds a different type, so you test it like any find. The same testing spelling exists for bagText, bagFlag, bagTile and bagBag.
  • bagNumberOr(b, "hp", 0)int — the fallback spelling, when a default reads better than a test; bagTextOr and bagFlagOr likewise.
  • The list reads (bagNumbers, bagTexts, bagTiles, bagBags) answer an empty list when missing, so a for over one is always safe.
let guard = newBag()
bagPut(guard, "name", "guard")
bagPut(guard, "hp", 30)
bagPut(guard, "alive", true)

# nest one bag inside another
let drop = newBag()
bagPut(drop, "item", "Coins")
bagPut(guard, "drop", drop)

print(bagNumberOr(guard, "hp", 0))            # 30
let d = bagBag(guard, "drop")
if d != none { print(bagTextOr(d, "item", "?")) }

Mutable, compared by identity

Unlike lists — which stay immutable, every change handing you a new list — a bag is mutable: bagPut changes the bag itself, and every name holding that bag sees the change. == on two bags asks "are these the same bag", not "do they hold the same fields". That is what makes a row in a table updatable in place: bagInc(row, "kills", 1) grows the number where it sits.

Tables — a list of bags

var rows = bagList() starts an empty table and append grows it — all the usual list commands work on a list<bag> (first, last, insert, take, …), and bag is a type you can write: fun f(bag b) -> int, list<bag> in a fun's signature. On top of that sit the query commands:

  • bagsWhere(rows, key, value) / bagsWhereNot — filter by a field (a number, text or flag).
  • findBag(rows, key, value)bag? — the first match, or none.
  • pluckNumbers(rows, key) / pluckTexts — one column as a list.
  • sumBags(rows, key) — fold a number column into a total.
  • sortBagsBy(rows, key) / sortBagsByDesc — order by a number column; rows missing the column sort last.
  • bagInc(b, key, by) — grow a number field in place.
A kill tracker table
script "Kill Table" author "you" version "1.0" description "one bag per kill, queried as a table" category "Demo" every 600
var kills = bagList()
on loop {
    let row = newBag()
    bagPut(row, "name", "guard")
    bagPut(row, "hp", 30)
    bagPut(row, "alive", false)
    kills = append(kills, row)

    let guards = bagsWhere(kills, "name", "guard")
    print("guards so far:", count(guards))
    print("total hp seen:", sumBags(kills, "hp"))
    let strongest = first(sortBagsByDesc(kills, "hp"))
    if strongest != none { print("strongest:", bagTextOr(strongest, "name", "?")) }
    return 600
}

Saving — a bag survives a restart

saveBag("key", b) writes the bag as JSON into the script's settings file; savedBag("key")bag? loads it back on a later run — none the first time, so the usual test covers the fresh start. Totals, per-target records, the last thing seen: anything worth remembering across runs fits.

JSON — two ways in

JSON text — a web reply, a saved file — can be read two ways. Path reads take one value straight off the text: jsonNumber(reply, "user.items[2].hp"). A path is dots for fields plus [index] for arrays, and a root-level array starts at the index: "[0]". The bag bridge turns the whole document into a bag — jsonToBag(text)bag? — and back: bagToJson(bag)string.

  • Whole numbers only. jsonNumber answers none for 12.5; jsonNumberScaled(text, path, 100) turns 12.5 into 1250.
  • Null is not missing. jsonHas counts a JSON null as present; jsonIsNull tells the two apart.
  • Parse once when reading many fields. Each path read re-parses the text from the top, so a document you take several values from is better turned into a bag first with jsonToBag.
Path reads
let doc = "{\"user\": {\"name\": \"mark\", \"hp\": 30}, \"xs\": [5, 6, 7]}"
if jsonIsValid(doc) {
    print(jsonText(doc, "user.name"))
    print(jsonNumber(doc, "user.hp"))
    print(jsonNumber(doc, "xs[1]"))
    print(sum(jsonNumbers(doc, "xs")))
}
Parse once, read many — and persistence
let b = jsonToBag("{\"hp\": 30, \"name\": \"guard\"}")
if b != none {
    print(bagNumberOr(b, "hp", 0))
    saveBag("lastSeen", b)
}
let back = savedBag("lastSeen")
if back != none { print("remembered:", bagTextOr(back, "name", "?")) }

The bag commands · group bags

bagBag returns bag?
bagBag(bag: bag, key: string) -> bag?

The bag nested under a key, or none when the key is missing or holds something that is not a bag.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
if bagBag(newBag(), "stats") == none { print("unset") }
bagBags returns list<bag>
bagBags(bag: bag, key: string) -> list<bag>

The list of bags stored under a key, or an empty list when the key is missing or holds something else — so a for loop over it is always safe.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
print(count(bagBags(newBag(), "rows")))
bagFlag returns bool?
bagFlag(bag: bag, key: string) -> bool?

The flag stored under a key, or none when the key is missing or holds something that is not a flag.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
if bagFlag(newBag(), "alive") == none { print("unset") }
bagFlagOr returns bool
bagFlagOr(bag: bag, key: string, fallback: bool) -> bool

The flag under a key, or the fallback when the key is missing or holds something else. Never none — the right shape for a condition.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
fallbackrequired bool the answer when the field is missing
Example
if bagFlagOr(newBag(), "alive", false) { print("up") }
bagHas returns bool
bagHas(bag: bag, key: string) -> bool

True when the bag has a field under that key, whatever its type.

ParameterTypeWhat it does
bagrequired bag the bag to ask
keyrequired string the field to look for
Example
if bagHas(newBag(), "hp") { print("set") }
bagInc does something
bagInc(bag: bag, key: string, by: int)

Adds to a number field in place, treating a missing or non-number field as 0 first — the per-row tally. Faults on overflow.

ParameterTypeWhat it does
bagrequired bag the bag to count on
keyrequired string the number field to grow
byrequired int what to add; negative subtracts
Example
bagInc(newBag(), "kills", 1)
bagKeys returns list<string>
bagKeys(bag: bag) -> list<string>

Every field name in the bag, in the order they were first put — walk it with for to see what a bag holds.

ParameterTypeWhat it does
bagrequired bag the bag to list
Example
for k in bagKeys(newBag()) { print(k) }
bagList returns list<bag>
bagList() -> list<bag>

An empty list of bags, ready to append rows into. Lists are immutable as ever: append answers a new list, so write table = append(table, row).

Example
let table = bagList()  print(count(bagList()), count(table))
bagNumber returns int?
bagNumber(bag: bag, key: string) -> int?

The number stored under a key, or none when the key is missing or holds something that is not a number.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
if bagNumber(newBag(), "hp") == none { print("unset") }
bagNumberOr returns int
bagNumberOr(bag: bag, key: string, fallback: int) -> int

The number under a key, or the fallback when the key is missing or holds something else. Never none — the right shape for a readout.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
fallbackrequired int the answer when the field is missing
Example
print(bagNumberOr(newBag(), "hp", 0))
bagNumbers returns list<int>
bagNumbers(bag: bag, key: string) -> list<int>

The list of numbers stored under a key, or an empty list when the key is missing or holds something else — so a for loop over it is always safe.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
print(count(bagNumbers(newBag(), "prices")))
bagOf returns bag
bagOf(key: string, value: int) -> bagbagOf(key: string, value: string) -> bagbagOf(key: string, value: bool) -> bag

A new bag already holding one number field — the short way to start one.

A new bag already holding one text field — the short way to start one.

A new bag already holding one flag field — the short way to start one.

ParameterTypeWhat it does
keyrequired string the first field's name
valuerequired int the number to store under it
Example
let b = bagOf("hp", 30)  print(b)
let b = bagOf("name", "guard")  print(b)
let b = bagOf("alive", true)  print(b)
bagPut does something
bagPut(bag: bag, key: string, value: int)bagPut(bag: bag, key: string, value: string)bagPut(bag: bag, key: string, value: bool)bagPut(bag: bag, key: string, value: tile)bagPut(bag: bag, key: string, value: bag)bagPut(bag: bag, key: string, value: list<int>)bagPut(bag: bag, key: string, value: list<string>)bagPut(bag: bag, key: string, value: list<tile>)bagPut(bag: bag, key: string, value: list<bag>)

Sets one field on a bag, replacing what was under the key. A bag holds up to 512 fields; past that this refuses rather than growing without end.

ParameterTypeWhat it does
bagrequired bag the bag to write into
keyrequired string the field's name, up to 128 characters
valuerequired int the number to store
Example
bagPut(newBag(), "hp", 30)
bagPut(newBag(), "name", "guard")
bagPut(newBag(), "alive", true)
bagPut(newBag(), "spot", myTile())
bagPut(newBag(), "stats", newBag())
bagPut(newBag(), "prices", range(0, 3))
bagPut(newBag(), "names", split("a,b", ","))
bagPut(newBag(), "route", path(myTile()))
bagPut(newBag(), "rows", bagList())
bagRemove does something
bagRemove(bag: bag, key: string)

Removes one field from a bag. Removing a field that is not there does nothing, so it is safe to tidy unconditionally.

ParameterTypeWhat it does
bagrequired bag the bag to take the field off
keyrequired string the field to remove; missing is fine
Example
bagRemove(newBag(), "stale")
bagSize returns int
bagSize(bag: bag) -> int

How many fields the bag holds right now.

ParameterTypeWhat it does
bagrequired bag the bag to measure
Example
print("fields", bagSize(newBag()))
bagText returns string?
bagText(bag: bag, key: string) -> string?

The text stored under a key, or none when the key is missing or holds something that is not text.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
if bagText(newBag(), "name") == none { print("unset") }
bagTextOr returns string
bagTextOr(bag: bag, key: string, fallback: string) -> string

The text under a key, or the fallback when the key is missing or holds something else. Never none — the right shape for a readout.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
fallbackrequired string the answer when the field is missing
Example
print(bagTextOr(newBag(), "name", "?"))
bagTexts returns list<string>
bagTexts(bag: bag, key: string) -> list<string>

The list of texts stored under a key, or an empty list when the key is missing or holds something else — so a for loop over it is always safe.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
print(count(bagTexts(newBag(), "names")))
bagTile returns tile?
bagTile(bag: bag, key: string) -> tile?

The tile stored under a key, or none when the key is missing or holds something that is not a tile.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
if bagTile(newBag(), "spot") == none { print("unset") }
bagTiles returns list<tile>
bagTiles(bag: bag, key: string) -> list<tile>

The list of tiles stored under a key, or an empty list when the key is missing or holds something else — so a for loop over it is always safe.

ParameterTypeWhat it does
bagrequired bag the bag to read
keyrequired string the field to look under
Example
print(count(bagTiles(newBag(), "route")))
bagWipe does something
bagWipe(bag: bag)

Empties a bag of every field, keeping the bag itself — every variable holding it now sees it empty.

ParameterTypeWhat it does
bagrequired bag the bag to empty
Example
bagWipe(newBag())
bagsWhere returns list<bag>
bagsWhere(rows: list<bag>, key: string, value: int) -> list<bag>bagsWhere(rows: list<bag>, key: string, value: string) -> list<bag>bagsWhere(rows: list<bag>, key: string, value: bool) -> list<bag>

The rows whose field equals a value, in their original order, the input unchanged. A row without the field never matches.

ParameterTypeWhat it does
rowsrequired list<bag> the table to filter
keyrequired string the field to test in every row
valuerequired int the number the field must equal
Example
print(count(bagsWhere(bagList(), "hp", 30)))
print(count(bagsWhere(bagList(), "name", "guard")))
print(count(bagsWhere(bagList(), "alive", true)))
bagsWhereAtLeast returns list<bag>
bagsWhereAtLeast(rows: list<bag>, key: string, threshold: int) -> list<bag>

The rows whose number under a key is at least the threshold, in their original order. A row without the column never matches.

ParameterTypeWhat it does
rowsrequired list<bag> the table to filter
keyrequired string the number column to test in every row
thresholdrequired int the number the column is measured against
Example
print(count(bagsWhereAtLeast(bagList(), "lvl", 60)))
bagsWhereAtMost returns list<bag>
bagsWhereAtMost(rows: list<bag>, key: string, threshold: int) -> list<bag>

The rows whose number under a key is at most the threshold, in their original order. A row without the column never matches.

ParameterTypeWhat it does
rowsrequired list<bag> the table to filter
keyrequired string the number column to test in every row
thresholdrequired int the number the column is measured against
Example
print(count(bagsWhereAtMost(bagList(), "lvl", 40)))
bagsWhereNot returns list<bag>
bagsWhereNot(rows: list<bag>, key: string, value: int) -> list<bag>bagsWhereNot(rows: list<bag>, key: string, value: string) -> list<bag>bagsWhereNot(rows: list<bag>, key: string, value: bool) -> list<bag>

The rows whose field does NOT equal a value — including rows without the field at all. The complement of bagsWhere.

ParameterTypeWhat it does
rowsrequired list<bag> the table to filter
keyrequired string the field to test in every row
valuerequired int the number the field must equal
Example
print(count(bagsWhereNot(bagList(), "hp", 30)))
print(count(bagsWhereNot(bagList(), "name", "guard")))
print(count(bagsWhereNot(bagList(), "alive", true)))
bagsWhereOver returns list<bag>
bagsWhereOver(rows: list<bag>, key: string, threshold: int) -> list<bag>

The rows whose number under a key is strictly over the threshold, in their original order. A row without the column never matches.

ParameterTypeWhat it does
rowsrequired list<bag> the table to filter
keyrequired string the number column to test in every row
thresholdrequired int the number the column is measured against
Example
print(count(bagsWhereOver(bagList(), "hp", 50)))
bagsWhereUnder returns list<bag>
bagsWhereUnder(rows: list<bag>, key: string, threshold: int) -> list<bag>

The rows whose number under a key is strictly under the threshold, in their original order. A row without the column never matches.

ParameterTypeWhat it does
rowsrequired list<bag> the table to filter
keyrequired string the number column to test in every row
thresholdrequired int the number the column is measured against
Example
print(count(bagsWhereUnder(bagList(), "hp", 10)))
copyBag returns bag
copyBag(bag: bag) -> bag

A new bag with the same fields as this one. Shallow: a nested bag is shared, not copied — copy it too if you need it separate.

ParameterTypeWhat it does
bagrequired bag the bag to copy
Example
let b2 = copyBag(newBag())  print(b2)
distinctBagsBy returns list<bag>
distinctBagsBy(rows: list<bag>, key: string) -> list<bag>

The rows with later duplicates of a column removed — the first row with each value stays, in order. Rows without the column all count as sharing one blank value, so the first of those stays too.

ParameterTypeWhat it does
rowsrequired list<bag> the table to thin
keyrequired string the column two rows may not share
Example
print(count(distinctBagsBy(bagList(), "name")))
findBag returns bag?
findBag(rows: list<bag>, key: string, value: int) -> bag?findBag(rows: list<bag>, key: string, value: string) -> bag?findBag(rows: list<bag>, key: string, value: bool) -> bag?

The FIRST row whose field equals a value, or none when no row matches — the table lookup, one call.

ParameterTypeWhat it does
rowsrequired list<bag> the table to search
keyrequired string the field to test in every row
valuerequired int the number the field must equal
Example
if findBag(bagList(), "hp", 30) == none { print("no row") }
if findBag(bagList(), "name", "guard") == none { print("no row") }
if findBag(bagList(), "alive", true) == none { print("no row") }
maxBagBy returns bag?
maxBagBy(rows: list<bag>, key: string) -> bag?

The row with the LARGEST number under a key — the leaderboard's top row. None when no row has the column. Ties answer the earliest row.

ParameterTypeWhat it does
rowsrequired list<bag> the table to search
keyrequired string the number column to compare
Example
if maxBagBy(bagList(), "xp") == none { print("no rows") }
meanBags returns int?
meanBags(rows: list<bag>, key: string) -> int?

The average of a number column across the rows that have it, rounded toward zero. None when no row has the column.

ParameterTypeWhat it does
rowsrequired list<bag> the table to fold
keyrequired string the number column to average
Example
print(meanBags(bagList(), "hp"))
mergeBags returns bag
mergeBags(base: bag, over: bag) -> bag

A new bag holding both bags' fields; where both have a key, 'over' wins. Neither input is changed.

ParameterTypeWhat it does
baserequired bag the bag whose fields come first
overrequired bag the bag whose fields win a clash
Example
print(mergeBags(newBag(), bagOf("hp", 30)))
minBagBy returns bag?
minBagBy(rows: list<bag>, key: string) -> bag?

The row with the SMALLEST number under a key — the whole row, not the number. None when no row has the column. Ties answer the earliest row.

ParameterTypeWhat it does
rowsrequired list<bag> the table to search
keyrequired string the number column to compare
Example
if minBagBy(bagList(), "hp") == none { print("no rows") }
newBag returns bag
newBag() -> bag

A fresh, empty bag — the script's own record. Give it fields with bagPut and read them back with the typed bag reads.

Example
let b = newBag()  print(newBag(), b)
pluckNumbers returns list<int>
pluckNumbers(rows: list<bag>, key: string) -> list<int>

One column of a table as a list: every row's number under the key, rows without one skipped — feed it to sum, mean or sorted.

ParameterTypeWhat it does
rowsrequired list<bag> the table to read down
keyrequired string the number column to collect
Example
print(sum(pluckNumbers(bagList(), "hp")))
pluckTexts returns list<string>
pluckTexts(rows: list<bag>, key: string) -> list<string>

One column of a table as a list: every row's text under the key, rows without one skipped — the text partner of pluckNumbers.

ParameterTypeWhat it does
rowsrequired list<bag> the table to read down
keyrequired string the text column to collect
Example
print(count(pluckTexts(bagList(), "name")))
sortBagsBy returns list<bag>
sortBagsBy(rows: list<bag>, key: string) -> list<bag>

The rows ordered by a number column, smallest first, rows without the column last, the input unchanged. sortBagsByDesc turns it round.

ParameterTypeWhat it does
rowsrequired list<bag> the table to order
keyrequired string the number column to order by, smallest first
Example
print(count(sortBagsBy(bagList(), "hp")))
sortBagsByDesc returns list<bag>
sortBagsByDesc(rows: list<bag>, key: string) -> list<bag>

The rows ordered by a number column, largest first, rows without the column last, the input unchanged — the leaderboard order.

ParameterTypeWhat it does
rowsrequired list<bag> the table to order
keyrequired string the number column to order by, largest first
Example
print(count(sortBagsByDesc(bagList(), "xp")))
sumBags returns int
sumBags(rows: list<bag>, key: string) -> int

The total of a number column across every row, rows without it counting 0 — the one-call table sum. Faults on overflow.

ParameterTypeWhat it does
rowsrequired list<bag> the table to fold
keyrequired string the number column to add up
Example
print(sumBags(bagList(), "xp"))

The json commands · group json

bagToJson returns string
bagToJson(bag: bag) -> string

The bag as compact JSON: nested bags nest, lists map item by item, tiles become {"x", "y"} objects. Refuses a bag that holds itself. The other half of jsonToBag — save it with the store, or print it.

ParameterTypeWhat it does
bagrequired bag the bag to write out
Example
print(bagToJson(bagOf("hp", 30)))
bagToJsonPretty returns string
bagToJsonPretty(bag: bag) -> string

The bag as indented JSON, for a person to read — the same shape bagToJson writes compactly.

ParameterTypeWhat it does
bagrequired bag the bag to write out
Example
print(bagToJsonPretty(bagOf("hp", 30)))
jsonBags returns list<bag>
jsonBags(json: string, path: string) -> list<bag>

Every object in the array at the path as a bag — the one call that turns a JSON table into rows. Items that are not objects are skipped.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to an array of objects
Example
for row in jsonBags("{\"xs\": [{}]}", "xs") { print(bagSize(row)) }
jsonCount returns int?
jsonCount(json: string, path: string) -> int?

How many items the array at the path holds, or how many fields the object does. None when the path leads nowhere or to a plain value.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to an array or object
Example
print(jsonCount("{\"xs\": [1, 2]}", "xs"))
jsonEscape returns string
jsonEscape(text: string) -> string

The text with JSON's escapes applied and quotes around it — for building a JSON string by hand without breaking on a quote in the data.

ParameterTypeWhat it does
textrequired string the raw text to make safe
Example
print(jsonEscape("he said \"hi\""))
jsonFlag returns bool?
jsonFlag(json: string, path: string) -> bool?

The true or false at a path, or none when the path leads nowhere or the value is not a boolean.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to the value
Example
print(jsonFlag("{\"ok\": true}", "ok"))
jsonHas returns bool
jsonHas(json: string, path: string) -> bool

True when the path leads to a value, of any type, including null. False for unparseable text — this never faults.

ParameterTypeWhat it does
jsonrequired string the JSON text to look in
pathrequired string the path to try, like "user.items[0].id"
Example
if jsonHas("{\"a\": 1}", "a") { print("there") }
jsonIsNull returns bool
jsonIsNull(json: string, path: string) -> bool

True when the path leads to an explicit null — which jsonHas counts as present and the typed reads answer none for. This is how the two are told apart.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to the value
Example
if jsonIsNull("{\"a\": null}", "a") { print("null") }
jsonIsValid returns bool
jsonIsValid(json: string) -> bool

True when the text parses as JSON at all — ask before reading a reply you do not trust.

ParameterTypeWhat it does
jsonrequired string the text to try as JSON
Example
if jsonIsValid("{}") { print("parses") }
jsonItem returns string?
jsonItem(json: string, path: string, index: int) -> string?

One item of the array at the path, as its own JSON — feed it back to the json reads to go deeper. None when there is no such item.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to an array
indexrequired int which item, counting from 0
Example
print(jsonItem("{\"xs\": [5, 6]}", "xs", 1))
jsonKeys returns list<string>
jsonKeys(json: string, path: string) -> list<string>

Every field name of the object at the path, in the document's order. Empty when the path leads anywhere else — safe to walk with for. A name holding a dot or bracket cannot go back into a path (they are the path's own punctuation); read such a document with jsonToBag instead.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to an object; "" for the whole document
Example
for k in jsonKeys("{\"a\": 1}", "") { print(k) }
jsonMinify returns string
jsonMinify(json: string) -> string

The same JSON with every needless space removed — the shape to save or send. Refuses text that is not JSON.

ParameterTypeWhat it does
jsonrequired string the JSON text to shrink
Example
print(jsonMinify("{ \"a\": 1 }"))
jsonNumber returns int?
jsonNumber(json: string, path: string) -> int?

The whole number at a path, or none when the path leads nowhere, the value is not a number, or it has a real fraction (12.0 is whole; 12.5 is not — use jsonNumberScaled for those).

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to the value, like "user.hp"
Example
print(jsonNumber("{\"hp\": 30}", "hp"))
jsonNumberScaled returns int?
jsonNumberScaled(json: string, path: string, scale: int) -> int?

The number at a path multiplied by 'scale' and rounded half-up — how a decimal crosses into a whole-number language. 12.5 at scale 100 is 1250. None when the path leads nowhere or is not a number; faults on overflow.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to the value
scalerequired int what to multiply by first, like 100 for two decimal places
Example
print(jsonNumberScaled("{\"gp\": 12.5}", "gp", 100))
jsonNumbers returns list<int>
jsonNumbers(json: string, path: string) -> list<int>

Every whole number in the array at the path, in order, anything else in the array skipped. Empty when the path is not an array — safe to walk.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to an array
Example
print(sum(jsonNumbers("{\"xs\": [1, 2]}", "xs")))
jsonPretty returns string
jsonPretty(json: string) -> string

The same JSON indented for a person to read. Refuses text that is not JSON, with the line and column of the problem.

ParameterTypeWhat it does
jsonrequired string the JSON text to lay out
Example
print(jsonPretty("{\"a\": 1}"))
jsonText returns string?
jsonText(json: string, path: string) -> string?

The text at a path. A string answers itself, unquoted; any other value answers as its own JSON, so there is always something to print. None when the path leads nowhere.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to the value
Example
print(jsonText("{\"name\": \"guard\"}", "name"))
jsonTexts returns list<string>
jsonTexts(json: string, path: string) -> list<string>

Every item of the array at the path as text: strings themselves, everything else as its own JSON. Empty when the path is not an array.

ParameterTypeWhat it does
jsonrequired string the JSON text to read
pathrequired string the path to an array
Example
for t in jsonTexts("{\"xs\": [\"a\"]}", "xs") { print(t) }
jsonToBag returns bag?
jsonToBag(json: string) -> bag?

The whole document as a bag: nested objects become nested bags, arrays become the closest list the language has, nulls are skipped and decimal numbers are kept as text. None when the text is not a JSON object — parse once, then use the typed bag reads.

ParameterTypeWhat it does
jsonrequired string the JSON text to convert
Example
let b = jsonToBag("{\"hp\": 30}")  if b != none { print(bagNumberOr(b, "hp", 0)) }