{"id":15827260,"url":"https://github.com/nikoksr/konfetty","last_synced_at":"2026-01-24T04:33:20.658Z","repository":{"id":250747867,"uuid":"835347387","full_name":"nikoksr/konfetty","owner":"nikoksr","description":"Zero-dependency, type-safe and powerful post-processing for your existing config solution in Go.","archived":false,"fork":false,"pushed_at":"2024-10-24T14:13:18.000Z","size":109,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-25T17:37:12.831Z","etag":null,"topics":["config","configuration-management","default-values","defaults","go","golang","koanf","post-processing","type-safety","viper","zero-dependency"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nikoksr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-29T16:41:35.000Z","updated_at":"2024-10-24T14:13:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"6685c807-dd43-4ab8-a810-eef3c65b6799","html_url":"https://github.com/nikoksr/konfetty","commit_stats":{"total_commits":25,"total_committers":1,"mean_commits":25.0,"dds":0.0,"last_synced_commit":"7ddb569ca8f10526e90e0d968a2a9a6a9989b187"},"previous_names":["nikoksr/konfetty"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikoksr%2Fkonfetty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikoksr%2Fkonfetty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikoksr%2Fkonfetty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikoksr%2Fkonfetty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nikoksr","download_url":"https://codeload.github.com/nikoksr/konfetty/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243681024,"owners_count":20330152,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["config","configuration-management","default-values","defaults","go","golang","koanf","post-processing","type-safety","viper","zero-dependency"],"created_at":"2024-10-05T10:02:40.028Z","updated_at":"2026-01-24T04:33:15.640Z","avatar_url":"https://github.com/nikoksr.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u0026nbsp;\n\u003ch1\u003ekonfetty 🎉\u003c/h1\u003e\n\u003cp\u003e\u003ci\u003eZero-dependency, type-safe and powerful post-processing for your existing config solution in Go.\u003c/i\u003e\u003c/p\u003e\n\n\u0026nbsp;\n\n[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go\u0026logoColor=white\u0026style=flat)](https://pkg.go.dev/github.com/nikoksr/konfetty)\n[![codecov](https://codecov.io/gh/nikoksr/konfetty/graph/badge.svg?token=lySNULyXHL)](https://codecov.io/gh/nikoksr/konfetty)\n[![Go Report Card](https://goreportcard.com/badge/github.com/nikoksr/konfetty)](https://goreportcard.com/report/github.com/nikoksr/konfetty)\n[![Maintainability](https://api.codeclimate.com/v1/badges/e87ea581a2617e6afb36/maintainability)](https://codeclimate.com/github/nikoksr/konfetty/maintainability)\n\u003c/div\u003e\n\n\u0026nbsp;\n\n# About \u003ca id=\"about\"\u003e\u003c/a\u003e\n\nKonfetty is a Go library that solves the challenge of managing default values in complex configuration hierarchies. Whether you're using Viper, Koanf, or any other configuration solution, Konfetty adds powerful post-processing capabilities while maintaining complete type safety.\n\nKey features:\n- 🔍 Recursively applies defaults through nested structures\n- 🏗️ Respects type hierarchies, allowing base type defaults to be overridden by more specific types\n- 🛡️ Maintains compile-time type safety and eliminates the need for error-prone struct tags\n- 🔌 Integrates with existing configuration loading solutions as a post-processing step\n- 🧩 Applies to any struct-based hierarchies, not just configurations (e.g., middleware chains, complex domain models)\n- 🔧 Supports custom transformations and validations as part of the processing pipeline\n\nKonfetty reduces the boilerplate typically associated with setting default values in complex Go struct hierarchies, allowing developers to focus on their core application logic rather than complex default value management.\n\n\u003e [!NOTE]\n\u003e Konfetty is designed for use in single-threaded contexts, typically during application startup for configuration processing. Each `Processor` instance should be used by a single goroutine.\n\n## Installation \u003ca id=\"installation\"\u003e\u003c/a\u003e\n\n```bash\ngo get -u github.com/nikoksr/konfetty\n```\n\n## Quick Start \u003ca id=\"quick-start\"\u003e\u003c/a\u003e\n\n```go\npackage main\n\nimport \"github.com/nikoksr/konfetty\"\n\ntype BaseDevice struct {\n    Enabled bool\n}\n\ntype LightDevice struct {\n    BaseDevice\n    Brightness int\n}\n\ntype ThermostatDevice struct {\n    BaseDevice\n    Temperature float64\n}\n\ntype RoomConfig struct {\n    Devices []any\n}\n\nfunc main() {\n    // Stub configuration, typically pre-populated by your config provider (e.g., Viper or Koanf)\n    cfg := \u0026RoomConfig{\n        Devices: []any{\n            // A light device that's enabled by default\n            \u0026LightDevice{BaseDevice: BaseDevice{Enabled: true}},\n\n            // A light device with a custom brightness\n            \u0026LightDevice{Brightness: 75},\n\n            // An empty thermostat device\n            \u0026ThermostatDevice{},\n        },\n    }\n\n    cfg, err := konfetty.FromStruct(cfg).\n        WithDefaults(\n            // Devices are disabled by default\n            BaseDevice{Enabled: false},\n\n            // Light devices have a default brightness of 50\n            LightDevice{Brightness: 50},\n\n            // Thermostat devices have a default temperature of 20.0 and are enabled by default\n            ThermostatDevice{\n                // Override the base device default for thermostats\n                BaseDevice: BaseDevice{Enabled: true},\n                Temperature: 20.0,\n            },\n        ).\n        WithTransformer(func(cfg *RoomConfig) {\n            // Optional custom transformation logic for more complex processing\n        }).\n        WithValidator(func(cfg *RoomConfig) error {\n            // Optional custom validation logic\n            return nil\n        }).\n        Build()\n\n    // Handle error ...\n\n    // The processed config would look like this:\n    //\n    // {\n    //   \"Devices\": [\n    //     {\n    //       // LightDevice\n    //       \"Enabled\": true,     // Kept original value\n    //       \"Brightness\": 50     // Used LightDevice default\n    //     },\n    //     {\n    //       // LightDevice\n    //       \"Enabled\": false,    // Used BaseDevice default\n    //       \"Brightness\": 75     // Kept original value\n    //     },\n    //     {\n    //       // ThermostatDevice\n    //       \"Enabled\": true,     // Used ThermostatDevice default, overriding BaseDevice default\n    //       \"Temperature\": 20.0  // Used ThermostatDevice default\n    //     }\n    //   ]\n    // }\n\n    // Continue using your config struct as usual ...\n}\n```\n\nIn this example, Konfetty automatically applies the `BaseDevice` defaults to all devices, then overlays the specific defaults for `LightDevice` and `ThermostatDevice`. This happens recursively through the entire `RoomConfig` structure while maintaining compile-time type safety.\n\n## How Konfetty Works \u003ca id=\"how-it-works\"\u003e\u003c/a\u003e\n\nKonfetty's approach to default values sets it apart:\n\n- Define defaults for base types once, and they'll be applied automatically throughout your struct hierarchy, even in nested slices of different types\n- Override lower-level defaults with more specific ones for fine-grained control\n- Have type safety enforced at compile time, eliminating the need for error-prone struct tags\n\nThe processing pipeline follows this order: Recursively apply defaults \u003e apply (optional) transformations \u003e run (optional) validations\n\n## Core Concepts \u003ca id=\"core-concepts\"\u003e\u003c/a\u003e\n\n### Default Value Resolution \u003ca id=\"cc-default-value-resolution\"\u003e\u003c/a\u003e\n\nKonfetty applies defaults in a specific order:\n\n1. Base type defaults are applied first\n2. More specific type defaults override base defaults\n3. Existing non-zero values are always preserved (e.g., values set by your configuration provider)\n4. Nested structures are processed recursively\n\n```go\n// Entity is our base type\ntype Entity struct {\n    Name       string\n    IsFriendly bool\n}\n\n// Companion is a more specific type that embeds Entity\ntype Companion struct {\n    Entity            // Base entity properties\n    LoyaltyLevel int\n}\n\nkonfetty.FromStruct(\u0026config).\n    WithDefaults(\n        // 1. Base type (Entity) defaults are applied first\n        Entity{\n            Name: \"Unknown Entity\", // Enforce all entities to have a default name\n        },\n\n        // 2. More specific type (Companion) defaults override base defaults\n        Companion{\n            Entity: Entity{\n                Name:       \"Dogmeat\",  // Overrides (Base-) Entity's name (\"Unknown Entity\")\n                IsFriendly: true,       // Overrides (Base-) Entity's default\n            },\n            LoyaltyLevel: 10,\n        },\n    )\n\n// Note: Any existing non-zero values in 'config' would be preserved\n// e.g., if config.Name was already set to \"Rex\", it would not be changed\n```\n\n### Type Safety \u003ca id=\"cc-type-safety\"\u003e\u003c/a\u003e\n\nUnlike solutions that rely on struct tags, Konfetty leverages Go's type system to enforce type safety at compile time. This prevents accidentally setting default values of the wrong type.\n\n```go\ntype KonfettyDummy struct {\n    Money int\n}\n\ntype StructTagDummy struct {\n    Money int `default:\"I'm a string\"` // This will compile but potentially cause runtime errors\n}\n\nkonfetty.FromStruct(\u0026KonfettyDummy{}).\n    WithDefaults(\n        KonfettyDummy{\n            Money: \"I'm a string\", // This will not compile\n        },\n    )\n```\n\n### Recursive Defaults \u003ca id=\"cc-recursive-defaults\"\u003e\u003c/a\u003e\n\nA common approach to supplying default values is defining a config struct instance with default values. For example:\n\n```go\ntype Config struct {\n    Version string\n    Enabled bool\n}\n\n// Define an instance of Config with default values that can be overridden by the config provider\nvar defaultConfig = Config{\n    Version: \"1.0\",\n    Enabled: true,\n}\n```\n\nHowever, this approach becomes problematic with nested structs or slices of structs. Consider this more complex example from another project:\n\n```go\ntype BaseProbe struct {\n    Name     string\n    Interval time.Duration\n}\n\ntype HTTPProbe struct {\n    BaseProbe\n    Host string\n}\n\n// ... Other probe types\n\ntype Config struct {\n    HTTPProbes []HTTPProbe\n    // ... Other probes and fields\n}\n```\n\nUsing the simple approach, you might try:\n\n```go\nvar defaultConfig = Config{\n    HTTPProbes: []HTTPProbe{\n        {\n            BaseProbe: BaseProbe{\n                Name:     \"Default HTTP Probe\",\n                Interval: 5 * time.Second,\n            },\n            Host: \"http://localhost\",\n        },\n    },\n}\n```\n\nBut what if your config file already contains HTTP Probes, particularly incomplete ones? For example:\n\n```yaml\nhttp_probes:\n  - name: \"Incomplete Probe #1\"\n    host: \"http://example.com\"\n  - name: \"Incomplete Probe #2\"\n```\n\nThe `defaultConfig.HTTPProbes` would be overwritten by the loaded values, leaving incomplete probes that could cause runtime errors. You'd need to manually merge default values with loaded values. This is where Konfetty shines, automatically applying default values to nested structs and slices at any depth.\n\n```go\nkonfetty.FromStruct(\u0026config).\n    WithDefaults(\n        // Set sane defaults for all BaseProbes\n        BaseProbe{\n            Interval: 5 * time.Second,\n        },\n\n        // Fine-tune defaults for HTTPProbes\n        HTTPProbe{\n            BaseProbe: BaseProbe{\n                Interval: 60 * time.Second, // Override BaseProbe default for all HTTPProbes\n            },\n            Host: \"http://localhost\",\n        },\n\n        // Define default Config structure for empty config files\n        Config{\n            HTTPProbes: []HTTPProbe{\n                {\n                    BaseProbe: BaseProbe{\n                        // Only need to define the name; Konfetty will apply other defaults\n                        Name: \"Default HTTP Probe\",\n                    },\n                },\n            },\n        },\n    ).\n    Build()\n```\n\nHere's how Konfetty handles different config file scenarios:\n\n#### Fully Populated Config File\n\nWhen all values are set in the config file, Konfetty doesn't need to apply defaults:\n\n```yaml\nhttp_probes:\n  - name: \"My Probe #1\"\n    host: \"http://example.com\"\n    interval: \"5s\"\n  - name: \"My Probe #2\"\n    host: \"http://localhost\"\n    interval: \"15s\"\n```\n\nFinal config struct after Konfetty processing:\n\n```go\nConfig{\n    HTTPProbes: []HTTPProbe{\n        {\n            BaseProbe: BaseProbe{\n                Name: \"My Probe #1\",\n                Interval: 5 * time.Second,\n            },\n            Host: \"http://example.com\",\n        },\n        {\n            BaseProbe: BaseProbe{\n                Name: \"My Probe #2\",\n                Interval: 15 * time.Second,\n            },\n            Host: \"http://localhost\",\n        },\n    },\n}\n```\n\n#### Incomplete Config File\n\nWith two incomplete probes (first missing `interval`, second missing `host`):\n\n```yaml\nhttp_probes:\n  - name: \"Incomplete Probe #1\"\n    host: \"http://example.com\"\n  - name: \"Incomplete Probe #2\"\n    interval: \"10s\"\n```\n\nFinal config struct after Konfetty processing:\n\n```go\nConfig{\n    HTTPProbes: []HTTPProbe{\n        {\n            BaseProbe: BaseProbe{\n                Name: \"Incomplete Probe #1\",  // Kept from config file\n                Interval: 60 * time.Second,   // Applied default\n            },\n            Host: \"http://example.com\",     // Kept from config file\n        },\n        {\n            BaseProbe: BaseProbe{\n                Name: \"Incomplete Probe #2\",  // Kept from config file\n                Interval: 10 * time.Second,   // Kept from config file\n            },\n            Host: \"http://localhost\",       // Applied default\n        },\n    },\n}\n```\n\n#### Empty Config File\n\nWith an empty config file:\n\n```yaml\n{}\n```\n\nFinal config struct after Konfetty processing:\n\n```go\nConfig{\n    HTTPProbes: []HTTPProbe{\n        {\n            BaseProbe: BaseProbe{\n                Name: \"Default HTTP Probe\",\n                Interval: 60 * time.Second,\n            },\n            Host: \"http://localhost\",\n        },\n    },\n}\n```\n\n## Integration \u003ca id=\"integration\"\u003e\u003c/a\u003e\n\nKonfetty complements your current config loading mechanism rather than replacing it. Use it as a post-processing step after loading your config with Viper, Koanf, or any other solution.\n\n### With Viper \u003ca id=\"integration-viper\"\u003e\u003c/a\u003e\n\n```go\nviper.ReadInConfig()\nviper.Unmarshal(\u0026config)\n\nconfig, err := konfetty.FromStruct(\u0026config).\n    WithDefaults(defaultConfig).\n    WithTransformer(transformer).\n    WithValidator(validator).\n    Build()\n```\n\n### With Koanf \u003ca id=\"integration-koanf\"\u003e\u003c/a\u003e\n\n```go\nk := koanf.New(\".\")\nk.Load(file.Provider(\"config.yaml\"), yaml.Parser())\nk.Unmarshal(\"\", \u0026config)\n\nconfig, err := konfetty.FromStruct(\u0026config).\n    WithDefaults(defaultConfig).\n    WithTransformer(transformer).\n    WithValidator(validator).\n    Build()\n```\n\n## Usage Examples \u003ca id=\"examples\"\u003e\u003c/a\u003e\n\n- [Simple Example](examples/simple/main.go): A basic example demonstrating Konfetty with a simple configuration structure\n- [Complex Example](examples/complex/main.go): A more complex example showcasing Konfetty's hierarchical default system\n- [Viper Integration](examples/viper/main.go): A complete example demonstrating Konfetty integration with Viper\n- [Koanf Integration](examples/koanf/main.go): A complete example demonstrating Konfetty integration with Koanf\n\n## Contributing \u003ca id=\"contributing\"\u003e\u003c/a\u003e\n\nContributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for more details.\n\n## Support \u003ca id=\"support\"\u003e\u003c/a\u003e\n\nIf you find this project useful, consider giving it a ⭐️! Your support helps bring more attention to the project, enabling further improvements.\n\nWhile you're here, check out my other work:\n\n- [nikoksr/notify](https://github.com/nikoksr/notify) - A dead simple Go library for sending notifications to various messaging services.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikoksr%2Fkonfetty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnikoksr%2Fkonfetty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikoksr%2Fkonfetty/lists"}