- D 100%
| example | ||
| source/jsonconfig | ||
| tests | ||
| .gitignore | ||
| dub.json | ||
| README.md | ||
jsonconfig
Чтение JSON-конфигураций в структуры D: проверка типов и диапазонов, обязательные ключи, комментарии в JSON и ошибки с точным путём до значения:
config.json: server.listen[1].port: значение 70000 вне диапазона ushort [0 .. 65535]
Подключение
Локально, без публикации в реестр:
# один раз зарегистрировать каталог библиотеки под версией
dub add-local /path/to/jsonconfig 0.1.0
# в проекте (версия обязательна, иначе dub пойдёт искать пакет в code.dlang.org)
dub add jsonconfig@0.1.0
Или явным путём в dub.json проекта:
"dependencies": { "jsonconfig": { "path": "../jsonconfig" } }
Или из git-репозитория (dub ≥ 1.23):
"dependencies": { "jsonconfig": { "repository": "git+https://example.com/jsonconfig.git", "version": "<commit>" } }
Использование
import jsonconfig;
enum LogLevel { trace, info, warn, error }
struct Listen {
@required string host;
ushort port = 8080; // значение по умолчанию
}
@strict struct AppConfig { // неизвестные ключи — ошибка
@required @notEmpty string name;
@key("log-level") LogLevel logLevel = LogLevel.info;
@computed!defaultDataDir string dataDir;
@required @notEmpty Listen[] listen;
Nullable!uint timeout; // нет ключа или null — isNull
string[string] env;
@ignore string loadedFrom; // не читается из файла
}
try {
auto cfg = loadConfig!AppConfig("config.json");
} catch (ConfigException e) {
stderr.writeln(e.msg); // e.configFile, e.keyPath, e.reason — по отдельности
}
Другие точки входа: parseConfig!T(text, name), deserialize!T(jsonValue), deserialize!T(config).
Атрибуты
| Атрибут | Где | Действие |
|---|---|---|
@required |
поле | ключ обязан быть |
@key("name") |
поле | имя ключа в JSON отличается от имени поля |
@computed!fn |
поле | нет ключа — значение вычисляет fn() |
@notEmpty |
строка, массив, словарь | если ключ есть, значение не пустое |
@ignore |
поле | поле не читается |
@validate!(fn, "текст") |
поле | проверка значения своей функцией (см. ниже) |
@strict |
структура | неизвестный ключ — ошибка (ловит опечатки) |
Противоречивые сочетания (@required + @computed, @notEmpty на int, @ignore +
@validate, два поля с одним ключом) — ошибка компиляции.
Проверка значений
@validate!fn вызывает fn со значением поля после чтения из JSON. Функция либо возвращает
bool (текст ошибки — второй аргумент атрибута), либо бросает исключение со своим текстом:
bool isPowerOfTwo(uint n) { return n != 0 && (n & (n - 1)) == 0; }
void checkPort(ushort p) { enforce(p >= 1024, "порты ниже 1024 требуют root"); }
struct Pool {
@validate!(inRange!(10, 20)) int workers = 10;
@validate!(n => n % 2 == 0, "должно быть чётным") int batch;
@validate!isPowerOfTwo uint bufferSize = 4096;
@validate!checkPort ushort port = 8080;
@validate!(matches!`[a-z][a-z0-9_]{2,15}`) string user;
@validate!(everyElement!(oneOf!("tcp", "udp"))) string[] protocols;
@validate!(inRange!(1, 3600)) Nullable!uint timeout; // null пропускается
}
app.json: workers: значение 8 вне допустимого диапазона [10 .. 20]
app.json: user: значение "Admin" не соответствует шаблону [a-z][a-z0-9_]{2,15}
- Проверка выполняется, только если ключ задан, и после проверки типа и диапазона типа.
Значения по умолчанию и
@computedне проверяются. - Атрибутов на поле может быть несколько, выполняются по порядку.
- Функция с неподходящей сигнатурой — ошибка компиляции.
Готовые проверки (jsonconfig.validators):
| Проверка | Действие |
|---|---|
inRange!(lo, hi) |
значение в диапазоне включительно |
oneOf!(a, b, ...) |
значение из перечня |
matches!pattern |
строка целиком соответствует регулярке; шаблон проверяется при компиляции (ctRegex, +~1 с к сборке на шаблон) |
everyElement!fn |
fn для каждого элемента массива или словаря |
В своих функциях с matchFirst не забывайте ^...$: без якорей совпадение ищется в любом
месте строки.
Типы
string, bool, все целые (с проверкой диапазона), float/double/real, enum
(по имени элемента), Nullable!T, T[], T[N] (с проверкой длины), T[string],
вложенные структуры, JSONValue (сырое значение) — в любых сочетаниях.
Свой тип читается через статический метод:
struct Seconds {
long value;
static Seconds fromConfig(JSONValue v) { /* "5m" → 300 */ }
}
Исключение из fromConfig превращается в ConfigException с путём до значения.
Формат файла
Обычный JSON плюс комментарии // ... и /* ... */. UTF-8 BOM в начале файла допускается.
Номера строк в ошибках разбора соответствуют исходному файлу.
Динамический доступ
auto cfg = new Config("config.json");
auto name = cfg.get!string("name");
auto port = cfg.get("port", 8080);
auto timeout = cfg.getOptional!int("timeout");
foreach (l; cfg.section("server").getSections("listen"))
writeln(l.as!Listen);
Разработка
dub test # тесты (каталог tests/ в сборку библиотеки не входит)
cd example && dub run # пример
dub run -- broken.json # пример сообщения об ошибке