d2lang/d2 · error

decode icon URL for stable diagram hash: got %d fields, want

Error message

decode icon URL for stable diagram hash: got %d fields, want %d

What it means

After unmarshaling, stableHashURL verifies the icon URL object contains exactly the fields in legacyURLFieldOrder so the hash stays stable across struct versions. This error fires when the field count differs, indicating the raw JSON has extra, missing, or renamed fields relative to the expected legacy layout.

Source

Thrown at d2target/d2target.go:355

	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 {
			return nil, fmt.Errorf("decode icon URL for stable diagram hash: missing field %q", name)
		}
		if i > 0 {
			out.WriteByte(',')
		}
		out.WriteByte('"')
		out.WriteString(name)
		out.WriteString(`":`)
		out.Write(value)
	}
	out.WriteByte('}')

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Compare the raw object's keys against legacyURLFieldOrder (d2target/d2target.go:227) and remove unexpected fields or supply the missing ones
  2. If you changed the URL struct, update legacyURLFieldOrder to match
  3. Invalidate/rebuild cached persisted diagrams produced by older versions
  4. Pin consistent d2 versions for serialization and hashing

Example fix

// before (extra field)
{"url":"https://x/i.svg","height":24}
// after
{"url":"https://x/i.svg"}
Defensive patterns

Strategy: validation

Validate before calling

var fields map[string]json.RawMessage
if err := json.Unmarshal(raw, &fields); err != nil {
    return err
}
if len(fields) != len(legacyURLFieldOrder) {
    return fmt.Errorf("field count %d != expected %d", len(fields), len(legacyURLFieldOrder))
}

Type guard

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

Try / catch

hashed, err := stableHashURL(raw)
if err != nil {
    if strings.Contains(err.Error(), "fields, want") {
        log.Printf("schema drift in icon URL JSON: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Passing an icon URL JSON object with a different number of keys than len(legacyURLFieldOrder) — extra fields, missing fields, or a struct version mismatch between serializer and hashing code.

Common situations: Mixing versions of d2target-generated data (old caches, persisted diagrams hashed with newer code), or adding a field to the URL struct without updating legacyURLFieldOrder.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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