XINCGer/Unity3DTraining · error · InvalidProtocolBufferException
Expected object value for Struct
Error message
Expected object value for Struct
What it means
google.protobuf.Struct's canonical JSON form is a plain JSON object. MergeStruct reads the first token and, if it is not StartObject, throws this InvalidProtocolBufferException. Struct is internally stored as a map<string, Value>, so its JSON must be an object of field names to arbitrary values.
Solutions
- Change the JSON so the Struct field is a JSON object: "metadata": {"k": "v"}.
- If arbitrary (non-object) values are intended, change the schema to use google.protobuf.Value for that field instead of Struct.
- Wrap scalar/array payloads in an object before parsing, e.g. {"items": [...]}.
- Catch InvalidProtocolBufferException around Parse and return guidance that Struct fields require JSON objects.
Example fix
// before: Struct given an array
{"metadata": ["a", "b"]}
// after
{"metadata": {"a": 1, "b": 2}} Defensive patterns
Strategy: validation
Validate before calling
// Struct fields must be JSON objects
function validateStructField(payload, name) {
const v = payload[name];
if (v !== undefined && (typeof v !== 'object' || v === null || Array.isArray(v))) {
throw new Error(`${name} (google.protobuf.Struct) must be a JSON object`);
}
} Type guard
function isStructLike(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); } Try / catch
try { var msg = JsonParser.Default.Parse<T>(json); }
catch (InvalidProtocolBufferException ex) when (ex.Message == "Expected object value for Struct") { /* return 400: Struct field requires JSON object */ } Prevention
- Remember Struct = object only; use google.protobuf.Value when any JSON value is allowed
- Validate metadata fields as objects at API ingress
- Unit-test payload builders that target Struct fields
When it happens
Trigger: Calling JsonParser.Parse where a google.protobuf.Struct field receives a JSON array, string, number, boolean, or null instead of an object, e.g. {"metadata": [1,2,3]}.
Common situations: Sending arrays or scalars to a Struct field that semantically should be an object; Confusion between google.protobuf.Value (accepts anything) and google.protobuf.Struct (object only); APIs that stringify metadata before parsing.
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
- Unexpected token type:
- Expected object value for Any
- Struct fields cannot have an empty key or a null value.
- Value message must contain a value for the oneof.
- Unexpected case in struct field:
AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12).
Data as JSON: /api/errors/a1454ed3a24281c8.
Report an issue: GitHub.
Appendix: source
Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs:474
{
var field = fields[Value.ListValueFieldNumber];
var list = NewMessageForField(field);
tokenizer.PushBack(firstToken);
Merge(list, tokenizer);
field.Accessor.SetValue(message, list);
return;
}
default:
throw new InvalidOperationException("Unexpected token type: " + firstToken.Type);
}
}
private void MergeStruct(IMessage message, JsonTokenizer tokenizer)
{
var token = tokenizer.Next();
if (token.Type != JsonToken.TokenType.StartObject)
{
throw new InvalidProtocolBufferException("Expected object value for Struct");
}
tokenizer.PushBack(token);
var field = message.Descriptor.Fields[Struct.FieldsFieldNumber];
MergeMapField(message, field, tokenizer);
}
private void MergeAny(IMessage message, JsonTokenizer tokenizer)
{
// Record the token stream until we see the @type property. At that point, we can take the value, consult
// the type registry for the relevant message, and replay the stream, omitting the @type property.
var tokens = new List<JsonToken>();
var token = tokenizer.Next();
if (token.Type != JsonToken.TokenType.StartObject)
{
throw new InvalidProtocolBufferException("Expected object value for Any");
}View on GitHub (pinned to 016f98412e)