[{"data":1,"prerenderedAt":498},["ShallowReactive",2],{"docs-\u002Fmodules\u002Fconfig-advanced":3},{"id":4,"title":5,"body":6,"description":488,"extension":489,"meta":490,"navTitle":491,"navigation":492,"path":493,"rawbody":494,"seo":495,"stem":496,"__hash__":497},"docs\u002Fmodules\u002Fconfig-advanced.md","Config Advanced",{"type":7,"value":8,"toc":469},"minimark",[9,13,17,22,34,60,81,84,88,91,95,102,112,119,123,130,136,139,143,149,155,161,165,171,177,180,203,207,218,221,227,238,244,250,256,261,267,270,276,280,283,408,414,420,423,429,447,451,454,460,463],[10,11,5],"h1",{"id":12},"config-advanced",[14,15,16],"p",{},"This page covers format selection, migrations, adapters, and runtime-aware\nreload patterns.",[18,19,21],"h2",{"id":20},"format-selection","Format Selection",[14,23,24,25,29,30,33],{},"If your ",[26,27,28],"code",{},"@ConfigFile"," uses ",[26,31,32],{},"{ext}",", MagicUtils can switch formats using:",[35,36,37,44,50,55],"ul",{},[38,39,40,43],"li",{},[26,41,42],{},"\u003Cconfig>.format"," next to the config file",[38,45,46,49],{},[26,47,48],{},"magicutils.format"," in the config root directory",[38,51,52],{},[26,53,54],{},"-Dmagicutils.config.format=...",[38,56,57],{},[26,58,59],{},"MAGICUTILS_CONFIG_FORMAT",[14,61,62,63,66,67,66,70,66,73,76,77,80],{},"Supported values include ",[26,64,65],{},"json",", ",[26,68,69],{},"jsonc",[26,71,72],{},"yml",[26,74,75],{},"yaml",", and ",[26,78,79],{},"toml"," depending\non the installed format helpers.",[14,82,83],{},"If multiple candidate files exist, MagicUtils picks one and logs a warning.",[18,85,87],{"id":86},"format-migration","Format Migration",[14,89,90],{},"When the selected format changes and an older format file exists, MagicUtils\ncan migrate the data into the new target file. This is useful when moving from\nJSONC to YAML or TOML without forcing users to recreate their configs.",[18,92,94],{"id":93},"schema-migrations","Schema Migrations",[14,96,97,98,101],{},"Register ordered ",[26,99,100],{},"ConfigMigration"," steps to evolve config schemas:",[103,104,110],"pre",{"className":105,"code":107,"language":108,"meta":109},[106],"language-java","manager.registerMigrations(MyConfig.class,\n        new ConfigMigration() {\n            public String fromVersion() { return \"0\"; }\n            public String toVersion() { return \"1\"; }\n            public void migrate(Map\u003CString, Object> root) {\n                root.put(\"enabled\", true);\n            }\n        }\n);\n","java","",[26,111,107],{"__ignoreMap":109},[14,113,114,115,118],{},"MagicUtils stores the current schema version in ",[26,116,117],{},"config-version",".",[18,120,122],{"id":121},"custom-adapters","Custom Adapters",[14,124,125,126,129],{},"Use ",[26,127,128],{},"ConfigAdapters.register(...)"," for custom value types:",[103,131,134],{"className":132,"code":133,"language":108,"meta":109},[106],"ConfigAdapters.register(Duration.class, new ConfigValueAdapter\u003C>() {\n    public Duration deserialize(Object value) { ... }\n    public Object serialize(Duration value) { ... }\n});\n",[26,135,133],{"__ignoreMap":109},[14,137,138],{},"Register adapters before the affected config class is first loaded.",[18,140,142],{"id":141},"change-subscriptions","Change Subscriptions",[14,144,125,145,148],{},[26,146,147],{},"subscribeChanges(...)"," when you want an unsubscribe handle:",[103,150,153],{"className":151,"code":152,"language":108,"meta":109},[106],"ListenerSubscription subscription = manager.subscribeChanges(MyConfig.class, (cfg, sections) -> {\n    \u002F\u002F apply live update\n});\n",[26,154,152],{"__ignoreMap":109},[14,156,125,157,160],{},[26,158,159],{},"onChange(...)"," when you only need fire-and-forget registration.",[18,162,164],{"id":163},"runtime-resource-binding","Runtime Resource Binding",[14,166,167,170],{},[26,168,169],{},"MagicRuntime"," can rebuild managed resources on matching config changes:",[103,172,175],{"className":173,"code":174,"language":108,"meta":109},[106],"MagicRuntimeConfigBinding\u003CServiceConfig, ReloadableClient> binding = runtime.bindConfig(\n        \"service.client\",\n        ServiceConfig.class,\n        config -> new ReloadableClient(config), \u002F\u002F ReloadableClient implements AutoCloseable\n        \"service\"\n);\n",[26,176,174],{"__ignoreMap":109},[14,178,179],{},"Useful properties of this pattern:",[35,181,182,188,194,197],{},[38,183,184,185],{},"the latest resource is accessible via ",[26,186,187],{},"binding.require()",[38,189,190,191],{},"the same resource is exposed through\n",[26,192,193],{},"runtime.requireNamedComponent(\"service.client\", ReloadableClient.class)",[38,195,196],{},"replaced resources are closed automatically",[38,198,199,202],{},[26,200,201],{},"binding.close()"," removes the named runtime component and stops listening for\nconfig changes",[18,204,206],{"id":205},"validation-and-constraints","Validation And Constraints",[208,209,211,214,215],"h3",{"id":210},"minvalue-maxvalue",[26,212,213],{},"@MinValue"," \u002F ",[26,216,217],{},"@MaxValue",[14,219,220],{},"Clamp numeric config values to a safe range on load:",[103,222,225],{"className":223,"code":224,"language":108,"meta":109},[106],"@ConfigValue(\"interval_seconds\")\n@MinValue(5)\n@MaxValue(3600)\nprivate int intervalSeconds = 30;\n",[26,226,224],{"__ignoreMap":109},[228,229,231],"callout",{"type":230},"tip",[14,232,233,234,237],{},"Out-of-range values are clamped to the range on load (a warning is logged). Set\n",[26,235,236],{},"warn = false"," on the annotation to suppress that log message.",[208,239,241],{"id":240},"saveto",[26,242,243],{},"@SaveTo",[14,245,246,247,249],{},"Store a field in a separate file instead of the main config document. The field\nis excluded from the main file, written to (and loaded back from) the path in\nthe annotation, and multiple fields sharing one ",[26,248,243],{}," path are collected into\na single document keyed by each field's config path:",[103,251,254],{"className":252,"code":253,"language":108,"meta":109},[106],"@ConfigValue(\"secrets\")\n@SaveTo(\"secrets.{ext}\")\nprivate Secrets secrets = new Secrets();\n",[26,255,253],{"__ignoreMap":109},[14,257,125,258,260],{},[26,259,32],{}," so the side file follows the active config format.",[208,262,264],{"id":263},"configserializable",[26,265,266],{},"@ConfigSerializable",[14,268,269],{},"Enable a class for use inside config lists or maps:",[103,271,274],{"className":272,"code":273,"language":108,"meta":109},[106],"@ConfigSerializable\npublic class ServerEntry {\n    @ConfigValue(\"name\")\n    private String name = \"\";\n}\n",[26,275,273],{"__ignoreMap":109},[208,277,279],{"id":278},"annotation-options","Annotation Options",[14,281,282],{},"Several annotations carry options that are easy to miss:",[284,285,286,305],"table",{},[287,288,289],"thead",{},[290,291,292,296,299,302],"tr",{},[293,294,295],"th",{},"Annotation",[293,297,298],{},"Option",[293,300,301],{},"Default",[293,303,304],{},"Effect",[306,307,308,329,352,370,389],"tbody",{},[290,309,310,316,321,326],{},[311,312,313],"td",{},[26,314,315],{},"@ConfigValue",[311,317,318],{},[26,319,320],{},"required",[311,322,323],{},[26,324,325],{},"false",[311,327,328],{},"Fail loading if the key is absent instead of using the field default.",[290,330,331,336,341,346],{},[311,332,333],{},[26,334,335],{},"@Comment",[311,337,338],{},[26,339,340],{},"above",[311,342,343],{},[26,344,345],{},"true",[311,347,348,349,351],{},"Place the comment above the key; ",[26,350,325],{}," puts it inline.",[290,353,354,358,363,367],{},[311,355,356],{},[26,357,28],{},[311,359,360],{},[26,361,362],{},"autoCreate",[311,364,365],{},[26,366,345],{},[311,368,369],{},"Write the file with defaults on first load when missing.",[290,371,372,376,381,386],{},[311,373,374],{},[26,375,28],{},[311,377,378],{},[26,379,380],{},"template",[311,382,383],{},[26,384,385],{},"\"\"",[311,387,388],{},"Seed a new file from a bundled template resource.",[290,390,391,396,401,405],{},[311,392,393],{},[26,394,395],{},"@ConfigReloadable",[311,397,398],{},[26,399,400],{},"notifyOnChange",[311,402,403],{},[26,404,345],{},[311,406,407],{},"Fire change listeners when a reloadable section changes.",[103,409,412],{"className":410,"code":411,"language":108,"meta":109},[106],"@ConfigFile(value = \"example.{ext}\", template = \"example-default.jsonc\")\n@ConfigReloadable(sections = {\"messages\"}, notifyOnChange = true)\npublic final class ExampleConfig {\n    @ConfigValue(value = \"api-key\", required = true)\n    private String apiKey;\n\n    @ConfigValue(\"greeting\")\n    @Comment(value = \"Shown to players\", above = true)\n    private String greeting = \"Hello\";\n}\n",[26,413,411],{"__ignoreMap":109},[208,415,417],{"id":416},"listprocessor",[26,418,419],{},"@ListProcessor",[14,421,422],{},"Apply per-item validation when loading lists:",[103,424,427],{"className":425,"code":426,"language":108,"meta":109},[106],"@ConfigValue(\"entries\")\n@ListProcessor(EntryProcessor.class)\nprivate List\u003CServerEntry> entries = new ArrayList\u003C>();\n",[26,428,426],{"__ignoreMap":109},[14,430,431,432,435,436,439,440,443,444,118],{},"The processor implements ",[26,433,434],{},"ListItemProcessor\u003CT>"," and returns ",[26,437,438],{},"ProcessResult.ok()",",\n",[26,441,442],{},"ProcessResult.modified(value)",", or ",[26,445,446],{},"ProcessResult.replaceWithDefault()",[18,448,450],{"id":449},"hot-reload","Hot Reload",[14,452,453],{},"Mark reloadable sections and listen for updates:",[103,455,458],{"className":456,"code":457,"language":108,"meta":109},[106],"@ConfigReloadable(sections = {\"messages\"})\npublic final class MyConfig { ... }\n",[26,459,457],{"__ignoreMap":109},[14,461,462],{},"Use section-aware reloads to avoid rebuilding unrelated services:",[103,464,467],{"className":465,"code":466,"language":108,"meta":109},[106],"manager.reload(MyConfig.class, \"messages\");\nmanager.reloadAsync(MyConfig.class, \"messages\");\nmanager.reloadSmart(MyConfig.class, \"messages\");\n",[26,468,466],{"__ignoreMap":109},{"title":109,"searchDepth":470,"depth":470,"links":471},3,[472,474,475,476,477,478,479,487],{"id":20,"depth":473,"text":21},2,{"id":86,"depth":473,"text":87},{"id":93,"depth":473,"text":94},{"id":121,"depth":473,"text":122},{"id":141,"depth":473,"text":142},{"id":163,"depth":473,"text":164},{"id":205,"depth":473,"text":206,"children":480},[481,483,484,485,486],{"id":210,"depth":470,"text":482},"@MinValue \u002F @MaxValue",{"id":240,"depth":470,"text":243},{"id":263,"depth":470,"text":266},{"id":278,"depth":470,"text":279},{"id":416,"depth":470,"text":419},{"id":449,"depth":473,"text":450},"Advanced MagicUtils config: migrations, comments, nested sections, and mixing JSON\u002FJSONC, YAML, and TOML formats in one project.","md",{},null,true,"\u002Fmodules\u002Fconfig-advanced","---\ntitle: Config Advanced\ndescription: 'Advanced MagicUtils config: migrations, comments, nested sections, and mixing JSON\u002FJSONC, YAML, and TOML formats in one project.'\n---\n\n# Config Advanced\n\nThis page covers format selection, migrations, adapters, and runtime-aware\nreload patterns.\n\n## Format Selection\n\nIf your `@ConfigFile` uses `{ext}`, MagicUtils can switch formats using:\n\n- `\u003Cconfig>.format` next to the config file\n- `magicutils.format` in the config root directory\n- `-Dmagicutils.config.format=...`\n- `MAGICUTILS_CONFIG_FORMAT`\n\nSupported values include `json`, `jsonc`, `yml`, `yaml`, and `toml` depending\non the installed format helpers.\n\nIf multiple candidate files exist, MagicUtils picks one and logs a warning.\n\n## Format Migration\n\nWhen the selected format changes and an older format file exists, MagicUtils\ncan migrate the data into the new target file. This is useful when moving from\nJSONC to YAML or TOML without forcing users to recreate their configs.\n\n## Schema Migrations\n\nRegister ordered `ConfigMigration` steps to evolve config schemas:\n\n```java\nmanager.registerMigrations(MyConfig.class,\n        new ConfigMigration() {\n            public String fromVersion() { return \"0\"; }\n            public String toVersion() { return \"1\"; }\n            public void migrate(Map\u003CString, Object> root) {\n                root.put(\"enabled\", true);\n            }\n        }\n);\n```\n\nMagicUtils stores the current schema version in `config-version`.\n\n## Custom Adapters\n\nUse `ConfigAdapters.register(...)` for custom value types:\n\n```java\nConfigAdapters.register(Duration.class, new ConfigValueAdapter\u003C>() {\n    public Duration deserialize(Object value) { ... }\n    public Object serialize(Duration value) { ... }\n});\n```\n\nRegister adapters before the affected config class is first loaded.\n\n## Change Subscriptions\n\nUse `subscribeChanges(...)` when you want an unsubscribe handle:\n\n```java\nListenerSubscription subscription = manager.subscribeChanges(MyConfig.class, (cfg, sections) -> {\n    \u002F\u002F apply live update\n});\n```\n\nUse `onChange(...)` when you only need fire-and-forget registration.\n\n## Runtime Resource Binding\n\n`MagicRuntime` can rebuild managed resources on matching config changes:\n\n```java\nMagicRuntimeConfigBinding\u003CServiceConfig, ReloadableClient> binding = runtime.bindConfig(\n        \"service.client\",\n        ServiceConfig.class,\n        config -> new ReloadableClient(config), \u002F\u002F ReloadableClient implements AutoCloseable\n        \"service\"\n);\n```\n\nUseful properties of this pattern:\n\n- the latest resource is accessible via `binding.require()`\n- the same resource is exposed through\n  `runtime.requireNamedComponent(\"service.client\", ReloadableClient.class)`\n- replaced resources are closed automatically\n- `binding.close()` removes the named runtime component and stops listening for\n  config changes\n\n## Validation And Constraints\n\n### `@MinValue` \u002F `@MaxValue`\n\nClamp numeric config values to a safe range on load:\n\n```java\n@ConfigValue(\"interval_seconds\")\n@MinValue(5)\n@MaxValue(3600)\nprivate int intervalSeconds = 30;\n```\n\n::callout{type=\"tip\"}\nOut-of-range values are clamped to the range on load (a warning is logged). Set\n`warn = false` on the annotation to suppress that log message.\n::\n\n### `@SaveTo`\n\nStore a field in a separate file instead of the main config document. The field\nis excluded from the main file, written to (and loaded back from) the path in\nthe annotation, and multiple fields sharing one `@SaveTo` path are collected into\na single document keyed by each field's config path:\n\n```java\n@ConfigValue(\"secrets\")\n@SaveTo(\"secrets.{ext}\")\nprivate Secrets secrets = new Secrets();\n```\n\nUse `{ext}` so the side file follows the active config format.\n\n### `@ConfigSerializable`\n\nEnable a class for use inside config lists or maps:\n\n```java\n@ConfigSerializable\npublic class ServerEntry {\n    @ConfigValue(\"name\")\n    private String name = \"\";\n}\n```\n\n### Annotation Options\n\nSeveral annotations carry options that are easy to miss:\n\n| Annotation | Option | Default | Effect |\n| --- | --- | --- | --- |\n| `@ConfigValue` | `required` | `false` | Fail loading if the key is absent instead of using the field default. |\n| `@Comment` | `above` | `true` | Place the comment above the key; `false` puts it inline. |\n| `@ConfigFile` | `autoCreate` | `true` | Write the file with defaults on first load when missing. |\n| `@ConfigFile` | `template` | `\"\"` | Seed a new file from a bundled template resource. |\n| `@ConfigReloadable` | `notifyOnChange` | `true` | Fire change listeners when a reloadable section changes. |\n\n```java\n@ConfigFile(value = \"example.{ext}\", template = \"example-default.jsonc\")\n@ConfigReloadable(sections = {\"messages\"}, notifyOnChange = true)\npublic final class ExampleConfig {\n    @ConfigValue(value = \"api-key\", required = true)\n    private String apiKey;\n\n    @ConfigValue(\"greeting\")\n    @Comment(value = \"Shown to players\", above = true)\n    private String greeting = \"Hello\";\n}\n```\n\n### `@ListProcessor`\n\nApply per-item validation when loading lists:\n\n```java\n@ConfigValue(\"entries\")\n@ListProcessor(EntryProcessor.class)\nprivate List\u003CServerEntry> entries = new ArrayList\u003C>();\n```\n\nThe processor implements `ListItemProcessor\u003CT>` and returns `ProcessResult.ok()`,\n`ProcessResult.modified(value)`, or `ProcessResult.replaceWithDefault()`.\n\n## Hot Reload\n\nMark reloadable sections and listen for updates:\n\n```java\n@ConfigReloadable(sections = {\"messages\"})\npublic final class MyConfig { ... }\n```\n\nUse section-aware reloads to avoid rebuilding unrelated services:\n\n```java\nmanager.reload(MyConfig.class, \"messages\");\nmanager.reloadAsync(MyConfig.class, \"messages\");\nmanager.reloadSmart(MyConfig.class, \"messages\");\n```\n",{"title":5,"description":488},"modules\u002Fconfig-advanced","ACM3-6Bw8jTCKEl68vDkbY9L5oFmBbrvx6Q8lhPXM-k",1783944487520]