Usage
The 17 examples below are taken from zig-config's README.
exe.root_module.addImport("zig-config", b.addModule("zig-config", .{
.root_source_file = b.path("path/to/zig-config/src/zig-config.zig"),
.target = target,
}));
const std = @import("std");
const zig_config = @import("zig-config");
// Define your configuration structure with full type safety!
const AppConfig = struct {
port: u16 = 8080,
debug: bool = false,
database: struct {
host: []const u8 = "localhost",
port: u16 = 5432,
} = .{},
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Load configuration with compile-time type checking
var config = try zig_config.loadConfig(AppConfig, allocator, .{
.name = "myapp",
});
defer config.deinit(allocator);
// Access values with full type safety - no runtime type checking needed!
const port: u16 = config.value.port; // Compile-time type safe!
const debug: bool = config.value.debug; // IDE autocomplete works!
const db_host = config.value.database.host; // Nested structs supported!
std.debug.print("Server running on port {d}\n", .{port});
}
const Config = struct {
// Required fields (no default)
app_name: []const u8,
// Optional fields
api_key: ?[]const u8 = null,
// Fields with defaults
port: u16 = 8080,
debug: bool = false,
// Nested structures
database: struct {
host: []const u8 = "localhost",
port: u16 = 5432,
pool_size: u32 = 10,
} = .{},
// Arrays
allowed_hosts: [][]const u8 = &.{},
// Complex types
timeouts: struct {
connect_ms: u32 = 5000,
read_ms: u32 = 30000,
} = .{},
};
var config = try zig_config.loadConfig(Config, allocator, .{
.name = "myapp",
});
defer config.deinit(allocator);
// All fields are type-safe!
std.debug.print("App: {s}, Port: {d}\n", .{
config.value.app_name,
config.value.port,
});
const Config = struct {
port: u16 = 8080,
debug: bool = false,
};
var config = try zig_config.loadConfig(Config, allocator, .{
.name = "myapp",
});
defer config.deinit(allocator);
// Fully typed access!
const port: u16 = config.value.port;
const Config = struct {
port: u16 = 8080,
};
var config = try zig_config.loadConfig(Config, allocator, .{
.name = "myapp",
.cwd = "/path/to/project",
});
defer config.deinit(allocator);
const Config = struct {
port: u16 = 8080,
};
var config = try zig_config.loadConfig(Config, allocator, .{
.name = "myapp",
.env_prefix = "CUSTOM", // Uses CUSTOM** instead of MYAPP**
});
defer config.deinit(allocator);
const std = @import("std");
const zig_config = @import("zig-config");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var target_map = std.json.ObjectMap.init(allocator);
defer target_map.deinit();
try target_map.put("a", .{ .integer = 1 });
var source_map = std.json.ObjectMap.init(allocator);
defer source_map.deinit();
try source_map.put("b", .{ .integer = 2 });
const merged = try zig_config.deepMerge(
allocator,
.{ .object = target_map },
.{ .object = source_map },
.{ .strategy = .smart }, // or .replace, .concat
);
_ = merged;
// Result: { "a": 1, "b": 2 }
}
.{ .strategy = .replace }
// Arrays are completely replaced
// [1, 2] + [3, 4] = [3, 4]
.{ .strategy = .concat }
// Arrays are concatenated with deduplication
// [1, 2] + [2, 3] = [1, 2, 3]
.{ .strategy = .smart }
// Object arrays are merged by key (id, name, key, path, type)
// [{"id": 1, "name": "a"}] + [{"id": 1, "name": "b"}]
// = [{"id": 1, "name": "b"}] // merged by id
pub fn ConfigResult(comptime T: type) type {
return struct {
value: T, // Your typed configuration
source: ConfigSource, // Primary source (.file_local, .file_home, .env_vars, .defaults)
sources: []SourceInfo, // All sources that contributed
loaded_at: i64, // Timestamp
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void;
};
}
pub const ZigConfigError = error{
ConfigFileNotFound,
ConfigFileInvalid,
ConfigFilePermissionDenied,
ConfigFileSyntaxError,
ConfigValidationFailed,
ConfigSchemaViolation,
EnvVarParseError,
CircularReferenceDetected,
MergeStrategyInvalid,
CacheError,
};
const AppConfig = struct {
port: u16 = 8080,
};
var config = zig_config.loadConfig(AppConfig, allocator, .{
.name = "myapp",
}) catch |err| switch (err) {
error.ConfigFileNotFound => {
std.debug.print("No config found, using defaults\n", .{});
return;
},
error.ConfigFileSyntaxError => {
std.debug.print("Invalid JSON in config file\n", .{});
return error.InvalidConfig;
},
else => return err,
};
defer config.deinit(allocator);
pub fn loadConfig(
comptime T: type,
allocator: std.mem.Allocator,
options: types.LoadOptions,
) !types.ConfigResult(T)
pub fn tryLoadConfig(
comptime T: type,
allocator: std.mem.Allocator,
options: types.LoadOptions,
) ?types.ConfigResult(T)
pub fn deepMerge(
allocator: std.mem.Allocator,
target: std.json.Value,
source: std.json.Value,
options: types.MergeOptions,
) !std.json.Value
pub const LoadOptions = struct {
name: []const u8,
defaults: ?std.json.Value = null,
cwd: ?[]const u8 = null,
validate: bool = true,
cache: bool = true,
cache_ttl: u64 = 300_000,
env_prefix: ?[]const u8 = null,
merge_strategy: MergeStrategy = .smart,
};
pub const MergeStrategy = enum {
replace,
concat,
smart,
};
pub const ConfigSource = enum {
file_local,
file_home,
package_json,
env_vars,
defaults,
};