cromo The Cromo Guide
PDF IT

JSON

JSON is how most services exchange data. In Cromo, the compiler writes the conversion between a struct and JSON by itself, the first time it is needed: no annotation is required on the struct, no code is generated by a separate tool, and the conversion is as fast as code written by hand.

#From structs to JSON and back

cromo
import std.json

struct Address {
    string city
    string country
}

struct Person {
    string name
    i64 born
    list<string> languages
    Address address
}

i32 main() {
    Person p
    p.name = "Grace"
    p.born = 1906
    p.languages = ["COBOL", "FLOW-MATIC"]
    p.address.city = "New York"
    p.address.country = "USA"

    string text = json.encode(p)
    println("{}", text)

    Person back = json.decode<Person>(text)
    println("{} was born in {}", back.name, back.born)
    ret 0
}
{"name":"Grace","born":1906,"languages":["COBOL","FLOW-MATIC"],"address":{"city":"New York","country":"USA"}}
Grace was born in 1906
  • json.encode(value) writes compact JSON; json.pretty(value) writes it on several lines, indented.
  • json.decode<T>(text) reads a T. A member that is missing, or null, leaves the default value of its field; a member without a field is ignored.
  • The conversion works for numbers, bool, string, lists, maps with string keys, fixed arrays, enums and structs whose fields can be converted. A field that cannot, as a function, is a compile error that names it.

#Names and fields to skip

The attribute [[json(...)]] before a field changes its name in JSON, or leaves it out:

cromo
import std.json

struct Account {
    [[json("user_id")]] i64 id
    string name
    [[json(skip)]] string password
}

i32 main() {
    Account a
    a.id = 7
    a.name = "Ada"
    a.password = "secret"
    println("{}", json.encode(a))
    ret 0
}
{"user_id":7,"name":"Ada"}

#Enums

A case without data is written as its name; a case with data as an object whose only member is the case:

cromo
import std.json

enum Shape {
    Empty
    Circle(f64 radius)
    [[json("rect")]] Rectangle(f64 width, f64 height)
}

i32 main() {
    list<Shape> shapes = [Shape.Empty, Shape.Circle(2.5), Shape.Rectangle(1.0, 2.0)]
    string text = json.encode(shapes)
    println("{}", text)
    list<Shape> back = json.decode<list<Shape>>(text)
    println("{:?}", back[2])
    ret 0
}
["Empty",{"Circle":{"radius":2.5}},{"rect":{"width":1.0,"height":2.0}}]
Rectangle(1.0, 2.0)

#Values of any shape

When the shape of the data is not known in advance, json.parse gives a json.Value, an enum with the cases Null, Bool, Int, UInt, Float, Text, Array and Object:

cromo
import std.json

i32 main() {
    json.Value v = json.parse("{\"items\": [1, 2.5, \"x\", null], \"ok\": true}")
    json.Value items = v.get("items")
    println("{} items, the first is {}", items.len(), items.at(0).asInt())
    println("{} {}", items.at(2).asText(), items.at(3).isNull())

    switch (items.at(1)) {
        case Int(i64 n) {
            println("an integer: {}", n)
        }
        case Float(f64 f) {
            println("a float: {}", f)
        }
        default {
            println("something else")
        }
    }

    json.Value made = json.object()
    made.set("language", json.Value.Text("Cromo"))
    made.set("version", json.Value.Int(27))
    println("{}", json.encode(made))
    ret 0
}
4 items, the first is 1
x true
a float: 2.5
{"language":"Cromo","version":27}

Integers and floating-point numbers are kept apart: an identifier of 64 bits keeps all its digits.

#Errors

A text that is not JSON, or JSON that does not fit the type, is an exception that says where:

cromo
import std.json

struct Point {
    i64 x
    i64 y
}

i32 main() {
    try {
        Point p = json.decode<Point>("{\"x\": 1, \"y\": \"two\"}")
    } catch (Exception e) {
        println("{}", e.message())
    }
    try {
        json.Value v = json.parse("{\"x\": 1,, }")
    } catch (Exception e) {
        println("{}", e.message())
    }
    ret 0
}
cannot decode JSON: at y: expected an integer, found a string
cannot read JSON: line 1, column 9: expected the name of a member, in double quotes

#Summary

  • json.encode, json.pretty and json.decode<T> convert structs, lists, maps and enums, with code written by the compiler.
  • [[json("name")]] renames a field or a case; [[json(skip)]] leaves a field out.
  • json.parse gives a json.Value for data of any shape.
  • Errors say where the problem is.