{"record":{"id":"3e7ec70d82349afb","repo":"HMCL-dev/HMCL","slug":"config-is-not-an-object","errorCode":null,"errorMessage":"Config is not an object: ","messagePattern":"Config is not an object: ","errorType":"exception","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/ObservableSetting.java","lineNumber":369,"sourceCode":"\n            JsonObject result = new JsonObject();\n            for (var field : fields) {\n                Observable observable = field.get(setting);\n                if (setting.tracker.isDirty(observable)) {\n                    field.serialize(result, setting, context);\n                }\n            }\n            setting.unknownFields.forEach(result::add);\n            return result;\n        }\n\n        @Override\n        public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (json == null || json.isJsonNull())\n                return null;\n\n            if (!json.isJsonObject())\n                throw new JsonParseException(\"Config is not an object: \" + json);\n\n            T setting = createInstance();\n            @SuppressWarnings(\"unchecked\")\n            var fields = (List<ObservableField<T>>) FIELDS.get(setting.getClass());\n\n            var values = new LinkedHashMap<>(json.getAsJsonObject().asMap());\n            for (ObservableField<T> field : fields) {\n                JsonElement value = values.remove(field.getSerializedName());\n                if (value == null) {\n                    for (String alternateName : field.getAlternateNames()) {\n                        value = values.remove(alternateName);\n                        if (value != null)\n                            break;\n                    }\n                }\n\n                if (value != null) {\n                    setting.tracker.markDirty(field.get(setting));","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/ObservableSetting.java#L351-L387","documentation":"The custom Gson deserializer for ObservableSetting subclasses requires the incoming JSON to be a JSON object, since each key is mapped to an ObservableField of the setting instance. When the JSON element is present but is an array, string, number, boolean, or other non-object value, the deserializer aborts with this JsonParseException before creating the instance.","triggerScenarios":"Gson deserializes a JSON document whose top-level (or nested, for a field of an ObservableSetting type) value is not an object — e.g. a JSON array, bare string, or number is fed to the adapter's deserialize() method.","commonSituations":"Hand-edited or corrupted config files where the settings object was replaced by an array or scalar; an API or tool emitting the wrong shape; deserializing the wrong type into an ObservableSetting-typed field.","solutions":["Inspect the JSON input and ensure the value at that position is a JSON object with keys matching the ObservableSetting fields.","Validate the JSON shape (e.g. element.isJsonObject()) before handing it to Gson.","Restore the config file from backup or regenerate it if it was corrupted or hand-mangled."],"exampleFix":"// before\ngson.fromJson(\"[1,2,3]\", MySetting.class); // throws 'Config is not an object'\n\n// after\nJsonElement el = JsonParser.parseString(raw);\nif (el.isJsonObject()) {\n    MySetting s = gson.fromJson(el, MySetting.class);\n}","handlingStrategy":"type-guard","validationCode":"JsonElement el = JsonParser.parseString(raw);\nif (!el.isJsonObject()) throw new IllegalArgumentException(\"Expected JSON object for config\");","typeGuard":"static boolean isSettingJson(JsonElement el) { return el != null && el.isJsonObject(); }","tryCatchPattern":"try { return gson.fromJson(raw, MySetting.class); } catch (JsonParseException e) { log.error(\"Bad config shape\", e); return defaultSetting(); }","preventionTips":["Validate JSON shape before deserialization.","Keep config files generated by the app, not hand-edited.","Keep a backup of known-good config files."],"tags":["json","gson","deserialization","type-mismatch"],"backgroundTag":"unexpected-response-shape","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}