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,
};