{"record":{"id":"50b425dd70b64bbf","repo":"HMCL-dev/HMCL","slug":"json-tostring-50b425","errorCode":null,"errorMessage":"json.toString()","messagePattern":"json\\.toString\\(\\)","errorType":"validation","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java","lineNumber":63,"sourceCode":"            JsonObject res = new JsonObject();\n            res.addProperty(\"os.name\", src.info().getPlatform().getOperatingSystem().getCheckedName());\n            res.addProperty(\"os.arch\", src.info().getPlatform().getArchitecture().getCheckedName());\n            res.addProperty(\"java.version\", src.info().getVersion());\n            res.addProperty(\"java.vendor\", src.info().getVendor());\n\n            if (src.update() != null)\n                res.add(\"update\", context.serialize(src.update()));\n\n            if (src.files() != null)\n                res.add(\"files\", context.serialize(src.files(), LOCAL_FILES_TYPE));\n\n            return res;\n        }\n\n        @Override\n        public JavaManifest deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n            if (!json.isJsonObject())\n                throw new JsonParseException(json.toString());\n\n            try {\n                JsonObject jsonObject = json.getAsJsonObject();\n                OperatingSystem osName = OperatingSystem.parseOSName(jsonObject.getAsJsonPrimitive(\"os.name\").getAsString());\n                Architecture osArch = Architecture.parseArchName(jsonObject.getAsJsonPrimitive(\"os.arch\").getAsString());\n                String javaVersion = jsonObject.getAsJsonPrimitive(\"java.version\").getAsString();\n                String javaVendor = Optional.ofNullable(jsonObject.get(\"java.vendor\")).map(JsonElement::getAsString).orElse(null);\n\n                Map<String, Object> update = jsonObject.has(\"update\") ? context.deserialize(jsonObject.get(\"update\"), Map.class) : null;\n                Map<String, JavaLocalFiles.Local> files = jsonObject.has(\"files\") ? context.deserialize(jsonObject.get(\"files\"), LOCAL_FILES_TYPE) : null;\n\n                if (osName == null || osArch == null || javaVersion == null)\n                    throw new JsonParseException(json.toString());\n\n                return new JavaManifest(new JavaInfo(Platform.getPlatform(osName, osArch), javaVersion, javaVendor), update, files);\n            } catch (JsonParseException e) {\n                throw e;\n            } catch (Throwable e) {","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java#L45-L81","documentation":"JavaManifest's Gson deserializer requires the JSON document to be a JSON object. If the top-level element is an array, string, number, or boolean, it throws JsonParseException whose message is the raw JSON (json.toString()). This fails fast before attempting to read the os.name/os.arch/java.version primitives.","triggerScenarios":"Gson deserializing a JavaManifest when the fetched JSON (e.g. a Mojang/BabaFX runtime manifest response) is not a JsonObject — a wrapped response, an error page, an array of manifests, or a corrupt/truncated file.","commonSituations":"Downloaded runtime manifest is actually an HTML error page or CDN error JSON; API endpoint changed and now returns an envelope object with the manifest nested one level down; manually edited/corrupted manifest cache file.","solutions":["Check the URL being fetched — ensure it points directly at the manifest JSON object, not a wrapper or error page","Log/inspect json.toString() from the exception message to see what was actually parsed and fix the source data","If the server wraps the manifest, deserialize the envelope first and pass the inner object to the JavaManifest adapter","Add a retry/refetch for the manifest in case of transient download corruption"],"exampleFix":"// before\nJavaManifest m = gson.fromJson(errorPageHtml, JavaManifest.class);\n// after\nString body = download(manifestUrl);\nif (!body.trim().startsWith(\"{\")) throw new IOException(\"Manifest is not JSON object: \" + body.substring(0, 80));\nJavaManifest m = gson.fromJson(body, JavaManifest.class);","handlingStrategy":"validation","validationCode":"String body = download(manifestUrl);\nJsonElement el = JsonParser.parseString(body);\nif (!el.isJsonObject()) throw new IOException(\"JavaManifest JSON is not an object\");","typeGuard":"static boolean isManifestShape(JsonElement el) {\n    return el.isJsonObject()\n        && el.getAsJsonObject().has(\"os.name\")\n        && el.getAsJsonObject().has(\"os.arch\");\n}","tryCatchPattern":"try {\n    JavaManifest m = gson.fromJson(body, JavaManifest.class);\n} catch (JsonParseException e) {\n    LOGGER.warning(\"Bad manifest payload: \" + e.getMessage().substring(0, Math.min(200, e.getMessage().length())));\n    // refetch or fall back to cached manifest\n}","preventionTips":["Check HTTP status and content-type before parsing the manifest body","Sniff that the response starts with '{' before handing it to Gson","Cache the last good manifest and fall back to it on parse failure","Pin the manifest URL to the exact runtime version"],"tags":["json","deserialization","manifest"],"backgroundTag":"json-parse-error","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"}