microsoft/typescript-go · error

cannot unmarshal non-object JSON value into Map

Error message

cannot unmarshal non-object JSON value into Map

What it means

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.

Source

Thrown at internal/collections/ordered_map.go:276

	panic("unexpected map key type")
}

var _ json.UnmarshalerFrom = (*OrderedMap[string, string])(nil)

func (m *OrderedMap[K, V]) UnmarshalJSONFrom(dec *json.Decoder) error {
	token, err := dec.ReadToken()
	if err != nil {
		return err
	}
	if token.Kind() == 'n' { // json.Null.Kind()
		// By convention, to approximate the behavior of Unmarshal itself,
		// Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op.
		// https://pkg.go.dev/encoding/json#Unmarshaler
		// TODO: reconsider
		return nil
	}
	if token.Kind() != '{' { // json.ObjectStart.Kind()
		return errors.New("cannot unmarshal non-object JSON value into Map")
	}
	for dec.PeekKind() != '}' { // json.ObjectEnd.Kind()
		var key K
		var value V
		if err := json.UnmarshalDecode(dec, &key); err != nil {
			return err
		}
		if err := json.UnmarshalDecode(dec, &value); err != nil {
			return err
		}
		m.Set(key, value)
	}
	if _, err := dec.ReadToken(); err != nil {
		return err
	}
	return nil
}

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Fix the producer to emit a JSON object ({"k": v, ...}) at that position
  2. If the input is genuinely an array, decode into a slice first and then insert entries into the map
  3. Pre-validate the payload shape (see validation) before handing it to Unmarshal

Example fix

// before
var m collections.OrderedMap[string, int]
err := m.UnmarshalJSON([]byte(`["a","b"]`)) // cannot unmarshal non-object JSON value into Map

// after
var m collections.OrderedMap[string, int]
err := m.UnmarshalJSON([]byte(`{"a":1,"b":2}`))
Defensive patterns

Strategy: type-guard

Validate before calling

// Pre-validate the top-level JSON shape before decoding into the Map.
func isJSONObject(b []byte) bool {
    for _, c := range b {
        switch c {
        case ' ', '\t', '\r', '\n':
            continue
        case '{':
            return true
        default:
            return false
        }
    }
    return false
}

Type guard

func isNonObjectJSON(err error) bool {
    return err != nil && strings.Contains(err.Error(), "cannot unmarshal non-object JSON value into Map")
}

Try / catch

var m collections.OrderedMap[string, int]
if err := m.UnmarshalJSON(payload); err != nil {
    if isNonObjectJSON(err) {
        // Producer sent an array: decode as slice and rebuild the map.
        var pairs []struct {
            K string `json:"k"`
            V int    `json:"v"`
        }
        if jerr := json.Unmarshal(payload, &pairs); jerr == nil {
            for _, p := range pairs { m.Set(p.K, p.V) }
        } else {
            return jerr
        }
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/d7ebafd11dfe6c3b. Report an issue: GitHub.