HMCL-dev/HMCL · error · JsonParseException

Theme-pack localized text must be a string or object

Error message

Theme-pack localized text must be a string or object: ${field}

What it means

Thrown by parseLocalizedText when a localized text field is neither a JSON string nor a JSON object. Only those two shapes are accepted; arrays, numbers, booleans, or null values for localized fields are rejected, with the field name in the message.

Solutions

  1. Use a plain string for non-localized text: "name": "My Theme".
  2. Or use a locale-to-string object: "name": { "en": "My Theme" }.
  3. Do not use arrays, numbers, booleans, or null for localized fields.

Example fix

// before
"name": ["My Theme"]
// after
"name": "My Theme"
Defensive patterns

Strategy: validation

Validate before calling

JsonElement el = manifestJson.get("name");
boolean ok = (el instanceof JsonPrimitive p && p.isString()) || el instanceof JsonObject;
if (!ok) {
    throw new IllegalArgumentException("localized field 'name' must be a string or object");
}

Type guard

static boolean isValidLocalizedShape(JsonElement el) {
    return (el instanceof JsonPrimitive p && p.isString()) || el instanceof JsonObject;
}

Try / catch

try {
    ThemePackManifest pack = gson.fromJson(json, ThemePackManifest.class);
} catch (JsonParseException e) {
    // fix the field named in the message to a string or locale map
}

Prevention

When it happens

Trigger: Deserializing a manifest where a localized field is e.g. "name": ["My Theme"], "name": 42, or "description": true — any value failing both the JsonPrimitive-string and JsonObject branches.

Common situations: Author wrapped the text in an array; a numeric or boolean placeholder was never replaced; a tool serialized localized text in a different representation than the theme-pack format.

Related errors


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

Appendix: source

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

        }
        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()) {
                    throw new JsonParseException("Localized text values must be strings: " + field);
                }
                localizedValues.put(
                        requireNonBlank(entry.getKey(), field),
                        requireNonBlank(primitive.getAsString(), field));
            }
            return new LocalizedText(localizedValues);
        }
        throw new JsonParseException("Theme-pack localized text must be a string or object: " + field);
    }

    /// Returns a validated localized text value.
    static LocalizedText requireLocalizedText(LocalizedText value, String field) {
        Objects.requireNonNull(value);

        JsonElement element = JsonUtils.GSON.toJsonTree(value, LocalizedText.class);
        return parseLocalizedText(element, field);
    }

    /// Returns a non-blank string value.
    private static String requireNonBlank(String value, String field) {
        String trimmed = value.trim();
        if (trimmed.isEmpty()) {
            throw new IllegalArgumentException("Theme-pack manifest field is blank: " + field);
        }
        return trimmed;
    }

View on GitHub (pinned to 24702dc5a0)