{"record":{"id":"0038a1687342277b","repo":"d2lang/d2","slug":"decode-icon-url-for-stable-diagram-hash-missing-f","errorCode":null,"errorMessage":"decode icon URL for stable diagram hash: missing field %q","messagePattern":"decode icon URL for stable diagram hash: missing field %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2target/d2target.go","lineNumber":363,"sourceCode":"\t}\n\treturn i, nil\n}\n\nfunc stableHashURL(raw json.RawMessage) ([]byte, error) {\n\tfields := make(map[string]json.RawMessage, len(legacyURLFieldOrder))\n\tif err := json.Unmarshal(raw, &fields); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode icon URL for stable diagram hash: %w\", err)\n\t}\n\tif len(fields) != len(legacyURLFieldOrder) {\n\t\treturn nil, fmt.Errorf(\"decode icon URL for stable diagram hash: got %d fields, want %d\", len(fields), len(legacyURLFieldOrder))\n\t}\n\n\tvar out bytes.Buffer\n\tout.WriteByte('{')\n\tfor i, name := range legacyURLFieldOrder {\n\t\tvalue, ok := fields[name]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"decode icon URL for stable diagram hash: missing field %q\", name)\n\t\t}\n\t\tif i > 0 {\n\t\t\tout.WriteByte(',')\n\t\t}\n\t\tout.WriteByte('\"')\n\t\tout.WriteString(name)\n\t\tout.WriteString(`\":`)\n\t\tout.Write(value)\n\t}\n\tout.WriteByte('}')\n\treturn out.Bytes(), nil\n}\n\nfunc (diagram Diagram) HasShape(condition func(Shape) bool) bool {\n\tfor _, d := range diagram.Layers {\n\t\tif d.HasShape(condition) {\n\t\t\treturn true\n\t\t}","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2target/d2target.go#L345-L381","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Make the JSON keys exactly match legacyURLFieldOrder names and order of appearance in that array","Fix key casing to match the expected field name reported in %q","Update legacyURLFieldOrder if a field was intentionally renamed","Regenerate the payload using d2's own serialization"],"exampleFix":"// before\n{\"Url\":\"https://x/i.svg\"}\n// after\n{\"url\":\"https://x/i.svg\"}","handlingStrategy":"validation","validationCode":"var fields map[string]json.RawMessage\nif err := json.Unmarshal(raw, &fields); err != nil {\n    return err\n}\nfor _, name := range legacyURLFieldOrder {\n    if _, ok := fields[name]; !ok {\n        return fmt.Errorf(\"icon URL JSON missing field %q\", name)\n    }\n}","typeGuard":"func hasAllLegacyFields(raw json.RawMessage) bool {\n    var m map[string]json.RawMessage\n    if json.Unmarshal(raw, &m) != nil {\n        return false\n    }\n    for _, name := range legacyURLFieldOrder {\n        if _, ok := m[name]; !ok {\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":"hashed, err := stableHashURL(raw)\nif err != nil {\n    log.Printf(\"icon URL field mismatch: %v\", err)\n    return err\n}","preventionTips":["Match key names/casing exactly to the struct json tags","Regenerate payloads after any struct rename","Never rename exported struct fields without updating legacyURLFieldOrder"],"tags":["json","hashing","schema-mismatch","d2target"],"backgroundTag":"schema-validation-failed","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}