OxL Reference

JSON and time

There and back#

Task. Turn a data structure into JSON and read it back.

oxl✓ checked in CI
use std.ser { toJson, fromJson }

enum Role { Admin; Member; Guest }

data Address { city: str; zip: str }

data User {
  id: i64
  name: str
  roles: List<Role>
  address: Address
  nickname: str?  // an optional field: none becomes null
  tags: Map<str, str>
}

fn main() io {
  let u = User{
    id: 7, name: "Ann", roles: [Role.Admin, Role.Member],
    address: Address{ city: "Paris", zip: "75001" },
    nickname: none, tags: {"team": "core"},
  }
  let text = toJson(&u)!  // any Serializable type serializes without extra code
  print(text)
  let back: User = fromJson(&text)!
  print("{back.name} {back.address.city} {back.roles.len()} {back.tags.get("team") ?? "-"}")
}
Output
{"id":"7","name":"Ann","roles":["Admin","Member"],"address":{"city":"Paris","zip":"75001"},"tags":{"team":"core"}}
Ann Paris 2 core
  • A type is serializable automatically if all its fields are: data, enum, List, Map, T?, nested records. No annotations or code are needed.
  • toJson(&u) returns str | EncodeError, fromJson(&text) returns T | DecodeError; the result type is set by the annotation let back: User = … or by an explicit fromJson<User>(…).
  • An empty nickname: str? is simply absent from the JSON. 64-bit integers are written as strings ("id":"7") - this keeps JSON safe for JavaScript, whose numbers are f64. Reading accepts both forms.
  • The same code works with the Msgpack and Cbor formats (std.ser): the format is a parameter, not a separate library.

Decoding errors#

Task. Accept data from an untrusted source and not crash.

oxl✓ checked in CI
use std.ser { fromJson }

data Point { x: i64; y: i64 }

fn parse(text: str) -> str {
  match fromJson<Point>(&text) {
    Point p => "ok {p.x},{p.y}"
    DecodeError => "decode error"  // DecodeError is an ordinary error value
  }
}

fn main() io {
  print(parse("{{\"x\":1,\"y\":2}}"))
  print(parse("{{\"x\":1}}"))  // field y is missing
  print(parse("{{\"x\":\"one\",\"y\":2}}"))  // wrong type
  print(parse("not json"))
}
Output
ok 1,2
decode error
decode error
decode error
  • Any problem - a missing field, a wrong type, a syntax error - gives a DecodeError; the program does not panic.
  • Refined types in fields (port: Port with where 1..=65535) are checked automatically on decoding (§9.4), so "validation" often comes down to choosing the right type.
  • In an HTTP handler (recipe) a DecodeError becomes a 400 response.

Dates and durations#

Task. Add durations, count calendar intervals and not get confused by leap years.

oxl✓ checked in CI
use std.time { Date }

fn main() io {
  let timeout = 90s + 500ms  // Duration literals: ms, s, m, h, d
  print("{timeout.millis()} ms = {timeout.seconds()} s")
  print("{(2h + 30m).minutes()} minutes")
  let d = Date.of(2024, 2, 28)!
  print("{d.toIso()} {d.weekday()} leap: {d.isLeapYear()}")
  print("{d.plusDays(2).toIso()} {(d + 1.month()).toIso()}")
  let other = Date.of(2025, 1, 1)!
  print("{d.daysUntil(other)} days until {other.toIso()}")
  match Date.of(2023, 2, 30) {
    Date x => print("valid {x.toIso()}")
    InvalidValue => print("no such date")
  }
  match Duration.parse("1h30m") {
    Duration x => print("{x.minutes()}")
    ParseError => print("bad duration")
  }
}
Output
90500 ms = 90 s
150 minutes
2024-02-28 Wed leap: true
2024-03-01 2024-03-28
308 days until 2025-01-01
no such date
90
  • The literals 90s, 500ms, 2h, 30m have the type Duration; they can be added and compared. Units cannot be mixed up because Duration is a separate type, not a number.
  • Date.of(2023, 2, 30) returns Date | InvalidValue: a nonexistent date cannot be created.
  • d + 1.month() is calendar arithmetic (Period), while plusDays(n) shifts by days; Duration is exact time (seconds), Period is calendar (months, years).
  • There are Tz and ZonedTime for time zones; std.time.now() reads the clock and needs the time effect, so pure functions receive the time as an argument (and are easily tested with virtual clocks, recipe).

Back: cookbook.

See also

OxL 1.0.0-beta · Documentation version 1.0.0-beta© OxwaldLink · generated from the Standard, the std sources and the guides