{"record":{"id":"d09df026207b7c92","repo":"d2lang/d2","slug":"decode-diagram-hash-json-object-key-has-type-t","errorCode":null,"errorMessage":"decode diagram hash JSON: object key has type %T","messagePattern":"decode diagram hash JSON: object key has type %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2target/d2target.go","lineNumber":266,"sourceCode":"\t\ttoken, err := dec.Token()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdelim, ok := token.(json.Delim)\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\n\t\tswitch delim {\n\t\tcase '{':\n\t\t\tfor dec.More() {\n\t\t\t\tkeyToken, err := dec.Token()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tkey, ok := keyToken.(string)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn fmt.Errorf(\"decode diagram hash JSON: object key has type %T\", keyToken)\n\t\t\t\t}\n\t\t\t\tif key != \"icon\" {\n\t\t\t\t\tif err := walkValue(); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tvalueStart, err := hashJSONValueStart(b, int(dec.InputOffset()))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tvar raw json.RawMessage\n\t\t\t\tif err := dec.Decode(&raw); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"decode icon URL for stable diagram hash: %w\", err)\n\t\t\t\t}\n\t\t\t\tif bytes.Equal(raw, []byte(\"null\")) {\n\t\t\t\t\tcontinue","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2target/d2target.go#L248-L284","documentation":"Stable diagram hashing walks the diagram JSON manually so the icon URL field can be canonicalized before hashing. While walking an object, each JSON key must be a string; if the decoder token is not a string, this error is returned. This only happens on malformed JSON, since encoding/json normally guarantees string keys in objects.","triggerScenarios":"Calling the stable-hash function (used by d2 for deterministic diagram hashes) on JSON that is corrupt, truncated mid-token, or produced by a non-standard encoder that emits non-string object keys.","commonSituations":"Patching diagram JSON with low-level tools before hashing; passing non-JSON bytes to the hasher; concurrency corruption of a shared byte buffer.","solutions":["Validate the JSON with json.Valid(b) before hashing","Re-serialize the diagram from the compiled d2target.Diagram instead of hashing hand-modified bytes","Ensure no concurrent writers are mutating the JSON buffer while hashing"],"exampleFix":"// before\nstable, err := StableHash(corruptBytes)\n// after\nif !json.Valid(b) { return errors.New(\"invalid diagram json\") }\nstable, err := StableHash(b)","handlingStrategy":"validation","validationCode":"if !json.Valid(b) {\n\treturn errors.New(\"diagram JSON is malformed\")\n}","typeGuard":null,"tryCatchPattern":"out, err := StableHashWithIcons(b)\nif err != nil && strings.Contains(err.Error(), \"decode diagram hash JSON\") {\n\t// regenerate diagram JSON and retry\n}","preventionTips":["Always validate JSON before hashing","Re-serialize diagrams instead of caching mutated bytes","Avoid concurrent writes to buffers being hashed"],"tags":["json","hashing","deserialization"],"backgroundTag":"malformed-json-input","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}