XINCGer/Unity3DTraining · error · InvalidProtocolBufferException

Expected an object to populate a map

Error message

Expected an object to populate a map

What it means

Google.Protobuf's JsonParser parses a proto3 map field strictly from a JSON object ({"key": value, ...}). When MergeMapField encounters any token other than '{' for a map field, it throws this InvalidProtocolBufferException. This mirrors the canonical protobuf JSON mapping where map fields must be represented as JSON objects.

Solutions

  1. Rewrite the JSON so the map field is a JSON object: "myMap": {"key1": "val1", "key2": "val2"} instead of an array.
  2. If the incoming JSON is array-encoded, pre-transform it to an object with a key extraction step before calling JsonParser.Parse.
  3. If the field is semantically a repeated message (ordered list) rather than a map, change the proto schema from map<K,V> to repeated <entry-message>.
  4. Catch InvalidProtocolBufferException around Parse and return a descriptive 400-style error identifying the malformed map field.

Example fix

// before: array encoding rejected
{"myMap": [{"key": "a", "value": 1}]}

// after: object encoding accepted
{"myMap": {"a": 1}}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure map fields are JSON objects before parsing
function validateMapsAsObjects(payload) {
  if (payload.myMap != null && (typeof payload.myMap !== 'object' || Array.isArray(payload.myMap))) {
    throw new Error('myMap must be a JSON object, not an array or scalar');
  }
}
// generic: if (Array.isArray(v)) v = Object.fromEntries(v.map(e => [e.key, e.value]));

Type guard

function isJsonObject(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); }

Try / catch

try { var msg = JsonParser.Default.Parse<MyMessage>(json); }
catch (InvalidProtocolBufferException ex) { /* log ex.Message; return 400 malformed map field */ }

Prevention

When it happens

Trigger: Calling JsonParser.Parse<T>(json) where a map<K,V> field is given a JSON array (e.g. [{...}]), a JSON array of key/value pairs, a string, a number, or null instead of a JSON object.

Common situations: Hand-written JSON payloads using array-of-entries notation for maps (a protobuf wire-format habit that is not valid canonical JSON); JSON produced by a non-canonical serializer that encodes maps as arrays; copy-pasted JSON from another serialization scheme (e.g. MessagePack-derived or MongoDB-style arrays).

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12). Data as JSON: /api/errors/41b7f51604fcfcfa. Report an issue: GitHub.

Appendix: source

Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs:277

                {
                    return;
                }
                tokenizer.PushBack(token);
                if (token.Type == JsonToken.TokenType.Null)
                {
                    throw new InvalidProtocolBufferException("Repeated field elements cannot be null");
                }
                list.Add(ParseSingleValue(field, tokenizer));
            }
        }

        private void MergeMapField(IMessage message, FieldDescriptor field, JsonTokenizer tokenizer)
        {
            // Map fields are always objects, even if the values are well-known types: ParseSingleValue handles those.
            var token = tokenizer.Next();
            if (token.Type != JsonToken.TokenType.StartObject)
            {
                throw new InvalidProtocolBufferException("Expected an object to populate a map");
            }

            var type = field.MessageType;
            var keyField = type.FindFieldByNumber(1);
            var valueField = type.FindFieldByNumber(2);
            if (keyField == null || valueField == null)
            {
                throw new InvalidProtocolBufferException("Invalid map field: " + field.FullName);
            }
            IDictionary dictionary = (IDictionary)field.Accessor.GetValue(message);

            while (true)
            {
                token = tokenizer.Next();
                if (token.Type == JsonToken.TokenType.EndObject)
                {
                    return;
                }

View on GitHub (pinned to 016f98412e)