HMCL-dev/HMCL · error · JsonSyntaxException

Unexpected token

Error message

Unexpected token 

What it means

The streaming variant LocalizedText.read (JsonReader-based) accepts null, a string, or an object of string values. If the next token is any other JSON token (BEGIN_ARRAY, NUMBER, BOOLEAN, etc.), it throws a JsonSyntaxException reporting the token.

Solutions

  1. Fix the JSON so the field is a string, an object of strings, or null.
  2. Peek the token (reader.peek()) and skip/handle non-supported shapes before calling read.
  3. Update the producer or adapter so the streaming path matches the actual data shape.

Example fix

// before
{"name": ["Hello"]} // BEGIN_ARRAY hits read()

// after
{"name": "Hello"} // or {"en": "Hello"}, or null
Defensive patterns

Strategy: type-guard

Validate before calling

if (reader.peek() != JsonToken.NULL && reader.peek() != JsonToken.STRING && reader.peek() != JsonToken.BEGIN_OBJECT) throw new IllegalStateException("Unsupported token for LocalizedText");

Type guard

static boolean isLocalizedToken(JsonReader r) throws IOException { JsonToken t = r.peek(); return t == JsonToken.NULL || t == JsonToken.STRING || t == JsonToken.BEGIN_OBJECT; }

Try / catch

try { return LocalizedText.read(reader); } catch (JsonSyntaxException e) { log.warn("Unexpected token for localized text", e); reader.skipValue(); return null; }

Prevention

When it happens

Trigger: A JsonReader positioned at a token other than NULL, STRING, or BEGIN_OBJECT when LocalizedText.read is invoked — most commonly an array at a localized-text field.

Common situations: JSON documents whose localized fields hold arrays or numbers; deserializing with the wrong adapter/type after a schema change; corrupted files.

Understand the failure class

Related errors


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

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/util/i18n/LocalizedText.java:110

            jsonReader.nextNull();
            return null;
        } else if (nextToken == JsonToken.STRING) {
            return new LocalizedText(jsonReader.nextString());
        } else if (nextToken == JsonToken.BEGIN_OBJECT) {
            LinkedHashMap<String, String> localizedValues = new LinkedHashMap<>();

            jsonReader.beginObject();
            while (jsonReader.hasNext()) {
                String name = jsonReader.nextName();
                String value = jsonReader.nextString();

                localizedValues.put(name, value);
            }
            jsonReader.endObject();

            return new LocalizedText(localizedValues);
        } else {
            throw new JsonSyntaxException("Unexpected token " + nextToken);
        }
    }

    /// Creates a locale-independent text value.
    ///
    /// @param value the text value
    /// @return a localized text object that serializes to a JSON string.
    public static LocalizedText plain(String value) {
        return new LocalizedText(value);
    }

    /// Locale-independent text used when this instance is not backed by localized values.
    private final @Nullable String value;

    /// Text values keyed by language keys generated by [LocaleUtils#toLanguageKey(Locale)].
    private final @Nullable @Unmodifiable Map<String, String> localizedValues;

    /// Creates a locale-independent text value.

View on GitHub (pinned to 24702dc5a0)