Skip to content
Kavachकवच

JSON: Json values

Also known as: dynamic type any unknown type duck typing schemaless untyped object dict mixed int float parse api response bigint decimal exact digits big number precision 0.1 lossless

● Intermediate4 min

A Json value holds JSON whose shape is known only while the program runs. It is a union of six cases, one for each kind of JSON value.

type Json = Null |
    Boolean(value: Bool) |
    Number(digits: String) |
    Text(value: String) |
    Array(items: [Json]) |
    Object(fields: [String: Json])
syntax: Kavach code

Being a union, match sees every case:

use std.json

fn main() ! Error {
    const text = "[1, 2.5, \"three\", true, null, [4]]"
    const items = try json.parse(text)
    for item in items.list() ?? [] {
        match item {
            Json.Number(digits) => print("number {digits}"),
            Json.Text(s) => print("text {s}"),
            Json.Boolean(b) => print("true or false: {b}"),
            Json.Null => print("nothing"),
            _ => print("a list or an object"),
        }
    }
}

The program ran and printed:

kavach run unknown-json.kav

number 1number 2.5text threetrue or false: truenothinga list or an object
unknown-json.kav: Kavach code, then what running it prints

Every method

MethodGives
text()String?: the text of a Text
int()Int?: a Number written without a fraction or exponent that fits Int
bigint()BigInt?: a Number written without a fraction or exponent, of any size
float()Float?: a Number, as the nearest Float; none past Float's range
decimal()Decimal?: a Number, exactly
bool()Bool?: the value of a Boolean
list()[Json]?: the items of an Array
object()[String: Json]?: the fields of an Object
is_null()Bool: whether it is Null
len()Int: the items of an Array or fields of an Object, otherwise 0

Numbers keep their digits

A Number keeps the digits it was written with, exactly, so no number is changed by reading it and writing it out again. Each method reads it as the type you need, and gives none when it doesn't fit:

use std.json

fn main() ! Error {
    const doc = try json.parse("\{\"id\": 12345678901234567890, \"price\": 0.1}")
    print(doc["id"].bigint())
    print(doc["id"].int())
    print(doc["price"].decimal())
    print(json.stringify(doc))
}

The program ran and printed:

kavach run exact-numbers.kav

12345678901234567890none0.1{"id":12345678901234567890,"price":0.1}
exact-numbers.kav: Kavach code, then what running it prints
  • The id is too big for Int, so .int() gives none; .bigint() takes any size.
  • .decimal() reads 0.1 exactly, where a Float holds only the nearest number to it.
  • Written out again, both are the same digits that came in.

.float() reads any number, whole or not; "free" isn't a number, so it adds 0.0:

use std.json

fn main() ! Error {
    const prices = try json.parse("[10, 2.5, \"free\", 7]")
    let total = 0.0
    for p in prices.list() ?? [] {
        total += p.float() ?? 0.0
    }
    print(total)
}

The program ran and printed:

kavach run unknown-numbers.kav

19.5
unknown-numbers.kav: Kavach code, then what running it prints

Parsing takes a number of any size. A number whose exponent puts it beyond 10^10_000, or below 10^-10_000, is out of range for .decimal() and .bigint() too, so a short text can't make a huge value:

use std.json

fn main() ! Error {
    const doc = try json.parse("[1e999999999]")
    print(doc[0].decimal())
    print(json.stringify(doc))
}

The program ran and printed:

kavach run huge.kav

none[1e999999999]
huge.kav: Kavach code, then what running it prints

JSON's null is the case Null. Kavach itself has no null: see Data Types for how it meets none.