{"record":{"id":"bd43768bfa0db39d","repo":"d2phap/ImageGlass","slug":"ige-could-not-parse-merged-config","errorCode":null,"errorMessage":"IGE: Could not parse merged config.","messagePattern":"IGE: Could not parse merged config\\.","errorType":"exception","errorClass":"FileLoadException","httpStatus":null,"severity":"critical","filePath":"source/ImageGlass.Lib/Settings/Config_Static.cs","lineNumber":589,"sourceCode":"            // 4. read igconfig.admin.json (BaseDir, then ConfigDir). Merge-only layer.\n            using var adminDoc = ReadAdminConfigDocument();\n\n            // 5. drop incompatible older layers; flag an incompatible user file so startup can warn\n            var effectiveDefaultDoc = IsCompatibleConfigLayer(defaultDoc) ? defaultDoc : null;\n            var effectiveAdminDoc = IsCompatibleConfigLayer(adminDoc) ? adminDoc : null;\n            var effectiveUserDoc = userDoc;\n            if (!IsCompatibleConfigLayer(userDoc))\n            {\n                effectiveUserDoc = null;\n                IncompatibleUserConfigPath = userConfigPath;\n            }\n\n            // 6. merge the compatible layers into a single JSON byte array\n            var mergedJson = MergeJsonLayers(effectiveDefaultDoc, effectiveUserDoc, cliOverrides, effectiveAdminDoc);\n\n            // 7. deserialize the merged JSON into Config\n            var config = JsonSerializer.Deserialize(mergedJson, jsonContext.Config)\n                ?? throw new FileLoadException(\"IGE: Could not parse merged config.\");\n\n            // 8. migrate if config version changed\n            appConfig = MigrateUserConfigFile(config);\n\n            // 9. apply persisted tool configs derived from the merged config\n            ApplyPersistedToolConfigs(appConfig);\n        }\n        catch (Exception ex)\n        {\n            LoadingException = ex;\n        }\n\n        appConfig ??= new();\n\n        // seed pre-configured external tools; also runs on a failed load so they still work\n        EnsurePredefinedTools(appConfig);\n\n        return appConfig;","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/d2phap/ImageGlass/blob/4a3c4feceffc5a8bb5e56ba836509634aaae47a9/source/ImageGlass.Lib/Settings/Config_Static.cs#L571-L607","documentation":"Thrown by Config_Static when JsonSerializer.Deserialize returns null after merging the default, user, CLI-override, and admin JSON config layers into a single byte array. The deserializer returned null rather than throwing, meaning the merged JSON parsed to a JSON null (or the merged byte array was empty/null) instead of a valid Config object. Wrapped in a FileLoadException and stored on LoadingException.","triggerScenarios":"Produced at Config_Static.cs:589 when JsonSerializer.Deserialize(mergedJson, jsonContext.Config) returns null. Reached at the end of config load after MergeJsonLayers — the merge produced a JSON document that deserializes to null.","commonSituations":"A hand-edited igconfig.json reduced to the literal `null`; a CLI override or admin layer that overwrote the whole object with null; MergeJsonLayers returning an empty byte array when all layers were null (e.g. first run with no default shipped); a corrupt default config shipped with the build; a JSON merge that produced `null` via deep override semantics.","solutions":["Inspect LoadingException.InnerException and the mergedJson bytes (log them at startup) to see if the merged document is `null`, empty, or a non-object.","Restore or regenerate igconfig.json — delete it so the app re-creates it from defaults on next start.","Ensure the default config layer (shipped with the app) is present and is a JSON object, not null.","If caused by CLI overrides (--Config:* args), check that the override syntax produces a value, not a whole-object replacement with null."],"exampleFix":"// before\nvar config = JsonSerializer.Deserialize(mergedJson, jsonContext.Config)\n    ?? throw new FileLoadException(\"IGE: Could not parse merged config.\");\n\n// after — include a snippet of the merged JSON to make the null parse diagnosable\nvar config = JsonSerializer.Deserialize(mergedJson, jsonContext.Config);\nif (config is null)\n{\n    var preview = mergedJson is null ? \"<null>\" : Encoding.UTF8.GetString(mergedJson);\n    if (preview.Length > 200) preview = preview[..200] + \"...\";\n    throw new FileLoadException($\"IGE: Could not parse merged config (parsed to null). Merged JSON: {preview}\");\n}","handlingStrategy":"try-catch","validationCode":"// Before deserialize, confirm the merged JSON is a non-null object document.\nif (mergedJson is null || mergedJson.Length == 0) throw new FileLoadException(\"Merged config is empty\");\nusing var doc = JsonDocument.Parse(mergedJson);\nif (doc.RootElement.ValueKind != JsonValueKind.Object) throw new FileLoadException($\"Merged config is {doc.RootElement.ValueKind}, expected object\");","typeGuard":"static bool IsMergedObject(byte[] json)\n{\n    if (json is null || json.Length == 0) return false;\n    using var d = JsonDocument.Parse(json);\n    return d.RootElement.ValueKind == JsonValueKind.Object;\n}","tryCatchPattern":"try { Config.Load(...); }\ncatch (FileLoadException ex) when (ex.Message.Contains(\"Could not parse merged config\"))\n{ _log.Error($\"Config parse failed: {ex.Message}\"); Config.RestoreDefaults(); /* or prompt user */ }","preventionTips":["Validate the merged JSON is an object (not null/empty/scalar) before deserializing.","Always ship a known-good default config layer so the merge never produces a null document.","Sanitize user-edited igconfig.json: reject literal `null` at the document root.","On parse failure, back up the corrupt user config and regenerate from defaults."],"tags":["config","json","serialization","startup","settings"],"backgroundTag":null,"analyzedSha":"4a3c4feceffc5a8bb5e56ba836509634aaae47a9","analyzedAt":"2026-08-13T16:58:15.523Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}