d2lang/d2 · error

decode icon URL for stable diagram hash: missing field %q

Error message

decode icon URL for stable diagram hash: missing field %q

What it means

When re-emitting the icon URL fields in canonical (legacy) order for hashing, stableHashURL requires every field in legacyURLFieldOrder to be present. Because Go maps lose ordering, the count check (error 192) may pass with the same number of fields only when names match exactly; if a name differs, the lookup by canonical name fails and reports the missing field.

Source

Thrown at d2target/d2target.go:363

	}
	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('}')
	return out.Bytes(), nil
}

func (diagram Diagram) HasShape(condition func(Shape) bool) bool {
	for _, d := range diagram.Layers {
		if d.HasShape(condition) {
			return true
		}

View on GitHub (pinned to 0d69dca6f5)

Solutions

  1. Make the JSON keys exactly match legacyURLFieldOrder names and order of appearance in that array
  2. Fix key casing to match the expected field name reported in %q
  3. Update legacyURLFieldOrder if a field was intentionally renamed
  4. Regenerate the payload using d2's own serialization

Example fix

// before
{"Url":"https://x/i.svg"}
// 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
}
for _, name := range legacyURLFieldOrder {
    if _, ok := fields[name]; !ok {
        return fmt.Errorf("icon URL JSON missing field %q", name)
    }
}

Type guard

func hasAllLegacyFields(raw json.RawMessage) bool {
    var m map[string]json.RawMessage
    if json.Unmarshal(raw, &m) != nil {
        return false
    }
    for _, name := range legacyURLFieldOrder {
        if _, ok := m[name]; !ok {
            return false
        }
    }
    return true
}

Try / catch

hashed, err := stableHashURL(raw)
if err != nil {
    log.Printf("icon URL field mismatch: %v", err)
    return err
}

Prevention

When it happens

Trigger: An icon URL JSON object whose keys don't match the exact names in legacyURLFieldOrder (renamed key, typo, different casing) — count matches but a canonical name lookup misses.

Common situations: Renaming struct fields in the URL type, casing mismatches from other serializers (e.g. camelCase vs snake_case), or hand-written JSON with a typo.

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/0038a1687342277b. Report an issue: GitHub.