Чтение JSON-конфигураций в структуры D
Find a file
2026-10-23 16:24:29 +03:00
example 0.1.0 2026-10-23 15:10:09 +03:00
source/jsonconfig 0.2.0 2026-10-23 16:24:29 +03:00
tests 0.2.0 2026-10-23 16:24:29 +03:00
.gitignore 0.1.0 2026-10-23 15:10:09 +03:00
dub.json 0.1.0 2026-10-23 15:10:09 +03:00
README.md 0.2.0 2026-10-23 16:24:29 +03:00

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       # пример сообщения об ошибке