{"record":{"id":"5b112e890db7e7cd","repo":"HMCL-dev/HMCL","slug":"game-directory-id-cannot-be-null","errorCode":null,"errorMessage":"Game directory ID cannot be null","messagePattern":"Game directory ID cannot be null","errorType":"validation","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/setting/GameDirectory.java","lineNumber":193,"sourceCode":"                if (name != null && !name.isJsonNull()) {\n                    jsonObject.add(\"name\", name);\n                }\n            }\n            jsonObject.add(\"path\", context.serialize(src.getPath(), PortablePath.class));\n            if (src.getLegacyGameSettings() != null) {\n                jsonObject.add(\"legacyGameSettings\", context.serialize(src.getLegacyGameSettings(), GameSettingsPresetID.class));\n            }\n\n            return jsonObject;\n        }\n\n        /// Deserializes a game directory from JSON.\n        @Override\n        public @Nullable GameDirectory deserialize(@Nullable JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (!(json instanceof JsonObject obj)) return null;\n            GameDirectoryID id = context.deserialize(obj.get(\"id\"), GameDirectoryID.class);\n            if (id == null) {\n                throw new JsonParseException(\"Game directory ID cannot be null\");\n            } else if (GameDirectoryID.NIL.equals(id)) {\n                throw new JsonParseException(\"Game directory ID cannot be nil\");\n            }\n            PortablePath path = context.deserialize(obj.get(\"path\"), PortablePath.class);\n            if (path == null) {\n                throw new JsonParseException(\"Game directory path cannot be null\");\n            }\n            @Nullable LocalizedText name = context.deserialize(obj.get(\"name\"), LocalizedText.class);\n\n            return new GameDirectory(id,\n                    name,\n                    path,\n                    context.deserialize(obj.get(\"legacyGameSettings\"), GameSettingsPresetID.class));\n        }\n\n    }\n}\n","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/setting/GameDirectory.java#L175-L211","documentation":"GameDirectory's Gson deserializer requires the \"id\" field of each game-directory entry to deserialize into a non-null GameDirectoryID. When the id is absent or unresolvable, context.deserialize returns null and the adapter throws JsonParseException(\"Game directory ID cannot be null\") instead of producing a GameDirectory with no identity. (JSON-null entries return null early only when the whole element is not an object.)","triggerScenarios":"Deserializing the HMCL settings JSON when a game-directory entry is a JsonObject without an \"id\" member, or with an \"id\" value that GameDirectoryID's adapter cannot decode to a non-null ID.","commonSituations":"Settings file written by an older HMCL version before IDs were introduced; hand-edited settings.json with an entry like {\"path\": \"...\"}; corrupted or truncated settings file; migration between versions where the id field was renamed.","solutions":["Add an \"id\" field with a valid unique ID to the game-directory entry in settings.json","If migrating from an old format, run the newer HMCL once on the old config via its migration path or re-add the directory in the UI","Back up and remove the malformed entry so HMCL recreates it with a generated ID","Check which HMCL version wrote the file and upgrade through intermediate versions rather than hand-editing"],"exampleFix":"// before\n{ \"path\": \"D:/minecraft\", \"name\": \"Main\" }\n// after\n{ \"id\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\", \"path\": \"D:/minecraft\", \"name\": \"Main\" }","handlingStrategy":"validation","validationCode":"for (JsonElement e : settings.getAsJsonArray(\"gameDirectories\")) {\n    if (e.isJsonObject() && (e.getAsJsonObject().get(\"id\") == null || e.getAsJsonObject().get(\"id\").isJsonNull()))\n        throw new IOException(\"Game directory entry missing id\");\n}","typeGuard":"static boolean hasDirectoryId(JsonObject entry) {\n    JsonElement id = entry.get(\"id\");\n    return id != null && !id.isJsonNull();\n}","tryCatchPattern":"try {\n    GameDirectory d = gson.fromJson(entry, GameDirectory.class);\n} catch (JsonParseException e) {\n    LOGGER.warning(\"Dropping malformed game directory: \" + e.getMessage());\n    // skip entry and re-add with generated id\n}","preventionTips":["Never hand-edit settings.json entries; use the UI","When copying entries, always copy or regenerate the id field","Back up settings.json before manual edits","Upgrade HMCL through its migration path instead of editing old formats directly"],"tags":["json","deserialization","settings","config"],"backgroundTag":"missing-required-config-field","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"}