XINCGer/Unity3DTraining · error · InvalidProtocolBufferException

Expected string value for Any.@type

Error message

Expected string value for Any.@type

What it means

Once MergeAny finds the "@type" property name, it expects the next token to be the type URL as a JSON string. If the value of @type is not a StringValue token (e.g. an object, array, number, or null), it throws this InvalidProtocolBufferException. The parser then resolves the type name via the TypeRegistry.

Solutions

  1. Make @type a plain JSON string: "@type": "type.googleapis.com/pkg.Type".
  2. Check middleware/serialization layers that may re-encode the @type value into an object or array.
  3. Validate at ingress that every Any object has @type as a string starting with the expected type URL prefix.
  4. After the string value, ensure settings.TypeRegistry contains the type name — a missing registration leads to the subsequent descriptor==null failure path.

Example fix

// before: non-string @type
{"detail": {"@type": {"url": "type.googleapis.com/foo.Bar"}}}

// after
{"detail": {"@type": "type.googleapis.com/foo.Bar", "id": 42}}
Defensive patterns

Strategy: validation

Validate before calling

// @type value must be a plain string
function validateTypeUrl(obj) {
  if (typeof obj?.['@type'] !== 'string') throw new Error('@type must be a string like type.googleapis.com/pkg.Type');
}

Type guard

function isStringTypeUrl(v) { return v !== null && typeof v === 'object' && typeof v['@type'] === 'string' && v['@type'].startsWith('type.googleapis.com/'); }

Try / catch

try { var msg = JsonParser.Default.Parse<T>(json); }
catch (InvalidProtocolBufferException ex) when (ex.Message == "Expected string value for Any.@type") { /* reject: @type must be a plain string */ }

Prevention

When it happens

Trigger: Parsing an Any field where @type has a non-string value, e.g. {"@type": 123}, {"@type": null}, or {"@type": {"url": "..."}}; also payloads where @type's value got wrapped or array-encoded.

Common situations: Templating bugs that interpolate the type URL incorrectly; serializers emitting type information as a structured object instead of a string; copy-paste mistakes where the @type value was quoted twice or object-encoded by middleware.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

            // for *this* Any.
            while (token.Type != JsonToken.TokenType.Name ||
                token.StringValue != JsonFormatter.AnyTypeUrlField ||
                tokenizer.ObjectDepth != typeUrlObjectDepth)
            {
                tokens.Add(token);
                token = tokenizer.Next();

                if (tokenizer.ObjectDepth < typeUrlObjectDepth)
                {
                    throw new InvalidProtocolBufferException("Any message with no @type");
                }
            }

            // Don't add the @type property or its value to the recorded token list
            token = tokenizer.Next();
            if (token.Type != JsonToken.TokenType.StringValue)
            {
                throw new InvalidProtocolBufferException("Expected string value for Any.@type");
            }
            string typeUrl = token.StringValue;
            string typeName = Any.GetTypeName(typeUrl);

            MessageDescriptor descriptor = settings.TypeRegistry.Find(typeName);
            if (descriptor == null)
            {
                throw new InvalidOperationException("Type registry has no descriptor for type name '" + typeName + "'");
            }

            // Now replay the token stream we've already read and anything that remains of the object, just parsing it
            // as normal. Our original tokenizer should end up at the end of the object.
            var replay = JsonTokenizer.FromReplayedTokens(tokens, tokenizer);
            var body = descriptor.Parser.CreateTemplate();
            if (descriptor.IsWellKnownType)
            {
                MergeWellKnownTypeAnyBody(body, replay);
            }

View on GitHub (pinned to 016f98412e)