JSON и время
Туда и обратно#
Задача. Превратить структуру данных в JSON и прочитать её обратно.
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? // необязательное поле: none превращается в 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)! // любой Serializable тип сериализуется без кода
print(text)
let back: User = fromJson(&text)!
print("{back.name} {back.address.city} {back.roles.len()} {back.tags.get("team") ?? "-"}")
}Вывод
{"id":"7","name":"Ann","roles":["Admin","Member"],"address":{"city":"Paris","zip":"75001"},"tags":{"team":"core"}}
Ann Paris 2 core- Тип сериализуем автоматически, если все его поля сериализуемы:
data,enum,List,Map,T?, вложенные записи. Никаких аннотаций и кода не нужно. toJson(&u)возвращаетstr | EncodeError,fromJson(&text)—T | DecodeError; тип результата задаётся аннотациейlet back: User = …или явнымfromJson<User>(…).- Пустой
nickname: str?в JSON просто отсутствует. Целые 64-битные числа записываются строками ("id":"7") — так JSON безопасен для JavaScript, у которого числа —f64. Обратное чтение принимает оба вида. - Тот же код работает с форматами Msgpack и Cbor (
std.ser): формат — параметр, а не отдельная библиотека.
Ошибки декодирования#
Задача. Принять данные из ненадёжного источника и не упасть.
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 — обычная ошибка-значение
}
}
fn main() io {
print(parse("{{\"x\":1,\"y\":2}}"))
print(parse("{{\"x\":1}}")) // нет поля y
print(parse("{{\"x\":\"one\",\"y\":2}}")) // неверный тип
print(parse("not json"))
}Вывод
ok 1,2 decode error decode error decode error
- Любая проблема — отсутствующее поле, неверный тип, синтаксис — даёт
DecodeError; программа не паникует. - Уточнённые типы в полях (
port: Portсwhere 1..=65535) проверяются при декодировании автоматически (§9.4), поэтому «валидация» часто сводится к выбору правильного типа. - В обработчике HTTP (рецепт)
DecodeErrorпревращается в ответ 400.
Даты и длительности#
Задача. Складывать длительности, считать календарные интервалы, не путаясь в високосных годах.
use std.time { Date }
fn main() io {
let timeout = 90s + 500ms // литералы Duration: 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")
}
}Вывод
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
- Литералы
90s,500ms,2h,30mимеют типDuration; они складываются и сравниваются. Единицы нельзя перепутать, потому чтоDuration— отдельный тип, а не число. Date.of(2023, 2, 30)возвращаетDate | InvalidValue: несуществующая дата не создаётся.d + 1.month()— календарная арифметика (Period), аplusDays(n)— сдвиг на дни;Duration— это точное время (секунды),Period— календарное (месяцы, годы).- Для часовых поясов есть
TzиZonedTime;std.time.now()читает часы и требует эффектаtime, поэтому чистые функции получают время аргументом (и легко тестируются виртуальными часами, рецепт).
Вернуться: кулинарная книга.