HMCL-dev/HMCL · error · JsonParseException

PortablePath must be a string: " + in.peek()

Error message

PortablePath must be a string: " + in.peek()

What it means

PortablePath's Gson TypeAdapter expects the JSON value for a path field to be a JSON string. This JsonParseException is thrown when the reader encounters a non-string, non-null token (number, boolean, object, array), since a PortablePath can only be deserialized from a string.

Solutions

  1. Fix the JSON so the path field is a string (add quotes around the value)
  2. Locate which file/field failed — the message includes the offending JsonToken — and correct that entry
  3. Restore the config from a known-good copy
  4. If a schema legitimately changed, update the adapter or mapping to handle the new shape

Example fix

// before
{ "path": 123 }
// after
{ "path": ".minecraft/mods" }
Defensive patterns

Strategy: validation

Validate before calling

// before Gson parsing, sanity-check path fields are strings
for (Map.Entry<String, JsonElement> e : obj.entrySet())
    if (e.getKey().toLowerCase().endsWith("path") && !e.getValue().isJsonPrimitive())
        throw new JsonSyntaxException(e.getKey() + " must be a string");

Type guard

static boolean isPortablePathJson(JsonElement el) {
    return el != null && el.isJsonPrimitive() && el.getAsJsonPrimitive().isString();
}

Try / catch

try {
    PortablePath p = gson.fromJson(json, Config.class).path;
} catch (JsonParseException e) {
    if (e.getMessage().startsWith("PortablePath must be a string")) restoreDefaultConfig();
    else throw e;
}

Prevention

When it happens

Trigger: Deserializing JSON (e.g. modpack/instance config) where a field mapped to PortablePath holds a number, boolean, object, or array instead of a string — usually a schema drift or corrupted/hand-edited config.

Common situations: Hand-edited config files quoting inconsistently; a schema change where a path field became an object; automated tools writing typed values into path fields.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/util/PortablePath.java:138

    /// Gson adapter that serializes portable paths as strings.
    @NotNullByDefault
    public static final class Adapter extends TypeAdapter<@Nullable PortablePath> {
        /// Writes a portable path as its stored path string, or JSON null when the value is null.
        @Override
        public void write(JsonWriter out, @Nullable PortablePath value) throws IOException {
            out.value(value == null ? null : value.getPath());
        }

        /// Reads a portable path from a string or JSON null.
        @Override
        public @Nullable PortablePath read(JsonReader in) throws IOException {
            if (in.peek() == JsonToken.NULL) {
                in.nextNull();
                return null;
            }
            if (in.peek() != JsonToken.STRING) {
                throw new JsonParseException("PortablePath must be a string: " + in.peek());
            }

            return PortablePath.of(in.nextString());
        }
    }
}

View on GitHub (pinned to 24702dc5a0)