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
- Rewrite the JSON so the map field is a JSON object: "myMap": {"key1": "val1", "key2": "val2"} instead of an array.
- If the incoming JSON is array-encoded, pre-transform it to an object with a key extraction step before calling JsonParser.Parse.
- 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>.
- 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
- Always serialize proto maps as JSON objects, never arrays of entries
- Document the canonical JSON mapping for map fields in API contracts
- Add JSON-schema validation that enforces type: object for map fields
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
- Map values must not be null
- Unhandled dictionary key type:
- Expected end of JSON after object
- Expected an object
- Unexpected token type
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)