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

  1. Change the JSON so the Struct field is a JSON object: "metadata": {"k": "v"}.
  2. If arbitrary (non-object) values are intended, change the schema to use google.protobuf.Value for that field instead of Struct.
  3. Wrap scalar/array payloads in an object before parsing, e.g. {"items": [...]}.
  4. 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

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


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)