{"record":{"id":"d7ebafd11dfe6c3b","repo":"microsoft/typescript-go","slug":"cannot-unmarshal-non-object-json-value-into-map","errorCode":null,"errorMessage":"cannot unmarshal non-object JSON value into Map","messagePattern":"cannot unmarshal non-object JSON value into Map","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/collections/ordered_map.go","lineNumber":276,"sourceCode":"\tpanic(\"unexpected map key type\")\n}\n\nvar _ json.UnmarshalerFrom = (*OrderedMap[string, string])(nil)\n\nfunc (m *OrderedMap[K, V]) UnmarshalJSONFrom(dec *json.Decoder) error {\n\ttoken, err := dec.ReadToken()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif token.Kind() == 'n' { // json.Null.Kind()\n\t\t// By convention, to approximate the behavior of Unmarshal itself,\n\t\t// Unmarshalers implement UnmarshalJSON([]byte(\"null\")) as a no-op.\n\t\t// https://pkg.go.dev/encoding/json#Unmarshaler\n\t\t// TODO: reconsider\n\t\treturn nil\n\t}\n\tif token.Kind() != '{' { // json.ObjectStart.Kind()\n\t\treturn errors.New(\"cannot unmarshal non-object JSON value into Map\")\n\t}\n\tfor dec.PeekKind() != '}' { // json.ObjectEnd.Kind()\n\t\tvar key K\n\t\tvar value V\n\t\tif err := json.UnmarshalDecode(dec, &key); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := json.UnmarshalDecode(dec, &value); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tm.Set(key, value)\n\t}\n\tif _, err := dec.ReadToken(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/collections/ordered_map.go#L258-L294","documentation":"OrderedMap's UnmarshalJSON (json/v2 decoding) requires the top-level JSON value to be an object; it explicitly allows null as a no-op but rejects arrays, strings, numbers, and booleans with this error. The map's key/value types are then decoded pairwise from the object body.","triggerScenarios":"Decoding a JSON array ('[{...}]') or scalar into an collections.OrderedMap / Map[K,V]; a server changing a map-typed field to a list; wrapping the payload in brackets when pretty-printing or proxying; decoding a JSON string produced by double-marshalling.","commonSituations":"API contract drift between versions where an object field becomes an array (or vice versa); hand-constructed JSON test fixtures; middleware that re-encodes bodies and accidentally changes the top-level shape.","solutions":["Fix the producer to emit a JSON object ({\"k\": v, ...}) at that position","If the input is genuinely an array, decode into a slice first and then insert entries into the map","Pre-validate the payload shape (see validation) before handing it to Unmarshal"],"exampleFix":"// before\nvar m collections.OrderedMap[string, int]\nerr := m.UnmarshalJSON([]byte(`[\"a\",\"b\"]`)) // cannot unmarshal non-object JSON value into Map\n\n// after\nvar m collections.OrderedMap[string, int]\nerr := m.UnmarshalJSON([]byte(`{\"a\":1,\"b\":2}`))","handlingStrategy":"type-guard","validationCode":"// Pre-validate the top-level JSON shape before decoding into the Map.\nfunc isJSONObject(b []byte) bool {\n    for _, c := range b {\n        switch c {\n        case ' ', '\\t', '\\r', '\\n':\n            continue\n        case '{':\n            return true\n        default:\n            return false\n        }\n    }\n    return false\n}","typeGuard":"func isNonObjectJSON(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"cannot unmarshal non-object JSON value into Map\")\n}","tryCatchPattern":"var m collections.OrderedMap[string, int]\nif err := m.UnmarshalJSON(payload); err != nil {\n    if isNonObjectJSON(err) {\n        // Producer sent an array: decode as slice and rebuild the map.\n        var pairs []struct {\n            K string `json:\"k\"`\n            V int    `json:\"v\"`\n        }\n        if jerr := json.Unmarshal(payload, &pairs); jerr == nil {\n            for _, p := range pairs { m.Set(p.K, p.V) }\n        } else {\n            return jerr\n        }\n    } else {\n        return err\n    }\n}","preventionTips":["Pin the API contract: map fields must always be JSON objects, never arrays","In tests, assert payload shape (`json.RawMessage` first byte is '{') before decoding into OrderedMap","Watch for double-encoded JSON (a string containing JSON), which also trips this check"],"tags":["json","deserialization","collections","contract-drift"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}