d2lang/d2 · error

decode icon URL for stable diagram hash: missing value

Error message

decode icon URL for stable diagram hash: missing value

What it means

This error comes from the internal parser that scans the raw JSON of an icon URL shape while building a stable diagram hash (hash must be identical across serialization variants). It skips whitespace after a key and, if the JSON ends before any value appears, it reports that the value is missing. It guards the hashing pipeline from truncated or hand-mangled raw JSON.

Source

Thrown at d2target/d2target.go:344

		return nil, err
	}
	out.Write(b[lastWrite:])
	return out.Bytes(), nil
}

func hashJSONValueStart(b []byte, keyEnd int) (int, error) {
	i := keyEnd
	for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\r' || b[i] == '\n') {
		i++
	}
	if i < len(b) && b[i] == ':' {
		i++
	}
	for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\r' || b[i] == '\n') {
		i++
	}
	if i >= len(b) {
		return 0, fmt.Errorf("decode icon URL for stable diagram hash: missing value")
	}
	return i, nil
}

func stableHashURL(raw json.RawMessage) ([]byte, error) {
	fields := make(map[string]json.RawMessage, len(legacyURLFieldOrder))
	if err := json.Unmarshal(raw, &fields); err != nil {
		return nil, fmt.Errorf("decode icon URL for stable diagram hash: %w", err)
	}
	if len(fields) != len(legacyURLFieldOrder) {
		return nil, fmt.Errorf("decode icon URL for stable diagram hash: got %d fields, want %d", len(fields), len(legacyURLFieldOrder))
	}

	var out bytes.Buffer
	out.WriteByte('{')
	for i, name := range legacyURLFieldOrder {
		value, ok := fields[name]
		if !ok {

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Inspect the raw icon URL JSON passed into hashing and make sure each key is followed by a complete value
  2. Regenerate the diagram JSON from the library instead of editing it by hand
  3. Validate the JSON parses with encoding/json before feeding it to stable hashing
  4. Check for code that trims or truncates serialized payloads before hashing

Example fix

// before
raw := []byte(`{"val":"`)
// after
raw := []byte(`{"val":"https://example.com/icon.svg"}`)
Defensive patterns

Strategy: validation

Validate before calling

raw := iconURLRawJSON
if !json.Valid(raw) {
    return fmt.Errorf("icon URL raw JSON is not valid JSON: %s", raw)
}
var probe map[string]json.RawMessage
if err := json.Unmarshal(raw, &probe); err != nil {
    return err
}
if len(probe) != len(legacyURLFieldOrder) {
    return fmt.Errorf("icon URL JSON has %d fields, want %d", len(probe), len(legacyURLFieldOrder))
}

Type guard

func isValidIconURLJSON(raw json.RawMessage) bool {
    if !json.Valid(raw) {
        return false
    }
    var probe map[string]json.RawMessage
    return json.Unmarshal(raw, &probe) == nil && len(probe) == len(legacyURLFieldOrder)
}

Try / catch

hashed, err := stableHashURL(raw)
if err != nil {
    log.Printf("icon URL hash skipped, malformed JSON %q: %v", raw, err)
    return err
}

Prevention

When it happens

Trigger: Calling the stable-hash code path with a raw JSON icon URL payload that ends right after a key/colon, i.e. truncated JSON with no value token before EOF.

Common situations: Manually constructed or partially written JSON for icon nearShape/shape URLs, external tools truncating output, or version drift where a serializer emits incomplete objects into the hash input.

Related errors


AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31). Data as JSON: /api/errors/0f52d4af85b90931. Report an issue: GitHub.