HMCL-dev/HMCL · error · JsonParseException

json.toString()

Error message

json.toString()

What it means

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.

Solutions

  1. Check the URL being fetched — ensure it points directly at the manifest JSON object, not a wrapper or error page
  2. Log/inspect json.toString() from the exception message to see what was actually parsed and fix the source data
  3. If the server wraps the manifest, deserialize the envelope first and pass the inner object to the JavaManifest adapter
  4. Add a retry/refetch for the manifest in case of transient download corruption

Example fix

// before
JavaManifest m = gson.fromJson(errorPageHtml, JavaManifest.class);
// after
String body = download(manifestUrl);
if (!body.trim().startsWith("{")) throw new IOException("Manifest is not JSON object: " + body.substring(0, 80));
JavaManifest m = gson.fromJson(body, JavaManifest.class);
Defensive patterns

Strategy: validation

Validate before calling

String body = download(manifestUrl);
JsonElement el = JsonParser.parseString(body);
if (!el.isJsonObject()) throw new IOException("JavaManifest JSON is not an object");

Type guard

static boolean isManifestShape(JsonElement el) {
    return el.isJsonObject()
        && el.getAsJsonObject().has("os.name")
        && el.getAsJsonObject().has("os.arch");
}

Try / catch

try {
    JavaManifest m = gson.fromJson(body, JavaManifest.class);
} catch (JsonParseException e) {
    LOGGER.warning("Bad manifest payload: " + e.getMessage().substring(0, Math.min(200, e.getMessage().length())));
    // refetch or fall back to cached manifest
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/50b425dd70b64bbf. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManifest.java:63

            JsonObject res = new JsonObject();
            res.addProperty("os.name", src.info().getPlatform().getOperatingSystem().getCheckedName());
            res.addProperty("os.arch", src.info().getPlatform().getArchitecture().getCheckedName());
            res.addProperty("java.version", src.info().getVersion());
            res.addProperty("java.vendor", src.info().getVendor());

            if (src.update() != null)
                res.add("update", context.serialize(src.update()));

            if (src.files() != null)
                res.add("files", context.serialize(src.files(), LOCAL_FILES_TYPE));

            return res;
        }

        @Override
        public JavaManifest deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            if (!json.isJsonObject())
                throw new JsonParseException(json.toString());

            try {
                JsonObject jsonObject = json.getAsJsonObject();
                OperatingSystem osName = OperatingSystem.parseOSName(jsonObject.getAsJsonPrimitive("os.name").getAsString());
                Architecture osArch = Architecture.parseArchName(jsonObject.getAsJsonPrimitive("os.arch").getAsString());
                String javaVersion = jsonObject.getAsJsonPrimitive("java.version").getAsString();
                String javaVendor = Optional.ofNullable(jsonObject.get("java.vendor")).map(JsonElement::getAsString).orElse(null);

                Map<String, Object> update = jsonObject.has("update") ? context.deserialize(jsonObject.get("update"), Map.class) : null;
                Map<String, JavaLocalFiles.Local> files = jsonObject.has("files") ? context.deserialize(jsonObject.get("files"), LOCAL_FILES_TYPE) : null;

                if (osName == null || osArch == null || javaVersion == null)
                    throw new JsonParseException(json.toString());

                return new JavaManifest(new JavaInfo(Platform.getPlatform(osName, osArch), javaVersion, javaVendor), update, files);
            } catch (JsonParseException e) {
                throw e;
            } catch (Throwable e) {

View on GitHub (pinned to 24702dc5a0)