HMCL-dev/HMCL · error · JsonParseException

Theme-pack manifest field must be a string

Error message

Theme-pack manifest field must be a string: ${fieldName}

What it means

Thrown by requireMemberString when a required manifest member exists but is not a JSON string primitive. Fields such as "id" and "version" must be plain strings; objects, arrays, numbers, booleans, or null values are rejected with the offending field name in the message.

Solutions

  1. Quote the value so it is a JSON string: "version": "1.0.0" instead of 1.0.
  2. Flatten any nested object into the expected string form.
  3. Validate the manifest with a JSON schema checker before packaging.

Example fix

// before
"version": 1.0
// after
"version": "1.0.0"
Defensive patterns

Strategy: validation

Validate before calling

for (String field : new String[]{"id", "version"}) {
    JsonElement el = manifestJson.get(field);
    if (el != null && !(el instanceof JsonPrimitive p && p.isString())) {
        throw new IllegalArgumentException(field + " must be a JSON string");
    }
}

Type guard

static boolean isStringField(JsonObject manifest, String field) {
    return manifest.get(field) instanceof JsonPrimitive p && p.isString();
}

Try / catch

try {
    ThemePackManifest pack = gson.fromJson(json, ThemePackManifest.class);
} catch (JsonParseException e) {
    // point the author at the offending field named in the message
}

Prevention

When it happens

Trigger: Deserializing a manifest with "id": 123, "version": { "major": 1 }, or "id": null — any case where `object.get(fieldName)` returns a non-string JsonPrimitive or non-primitive.

Common situations: Author wrote a version as a number (1.0 instead of "1.0"); nested metadata objects from another tool's format pasted into the manifest; a placeholder null left in place of a value.

Related errors


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

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManifest.java:197

        for (Theme theme : themes) {
            if (theme.id() == null) {
                throw new IllegalArgumentException("Theme ID is required when a theme pack declares multiple themes");
            }
            if (theme.name() == null) {
                throw new IllegalArgumentException("Theme name is required when a theme pack declares multiple themes");
            }
        }
    }

    /// Reads a required string member.
    private static String requireMemberString(JsonObject object, String fieldName) {
        JsonElement element = object.get(fieldName);
        if (element == null) {
            throw new JsonParseException("Theme-pack manifest is missing " + fieldName);
        }
        if (!(element instanceof JsonPrimitive primitive) || !primitive.isString()) {
            throw new JsonParseException("Theme-pack manifest field must be a string: " + fieldName);
        }
        return requireNonBlank(primitive.getAsString(), fieldName);
    }

    /// Parses a localized text value.
    static LocalizedText parseLocalizedText(JsonElement element, String field) {
        if (element instanceof JsonPrimitive primitive && primitive.isString()) {
            return LocalizedText.plain(requireNonBlank(primitive.getAsString(), field));
        }
        if (element instanceof JsonObject localizedObject) {
            if (localizedObject.isEmpty()) {
                throw new JsonParseException("Localized text field is empty: " + field);
            }

            LinkedHashMap<String, String> localizedValues = new LinkedHashMap<>();
            for (Map.Entry<String, JsonElement> entry : localizedObject.entrySet()) {
                JsonElement value = entry.getValue();
                if (!(value instanceof JsonPrimitive primitive) || !primitive.isString()) {

View on GitHub (pinned to 24702dc5a0)