XINCGer/Unity3DTraining · error · InvalidProtocolBufferException

Expected end of JSON after object

Error message

Expected end of JSON after object

What it means

JsonParser.Merge parses one protobuf message from a JSON document; after the top-level object closes, it expects the underlying reader/tokenizer to be exhausted (EndDocument). If extra tokens remain after the message object, it throws InvalidProtocolBufferException, because JSON input representing exactly one message must contain nothing else.

Solutions

  1. Ensure the JSON string contains exactly one top-level object with no trailing content
  2. If handling multiple messages, split the JSON first (e.g. deserialize to a JSON array, parse each element) or use a tokenizer/stream framing approach
  3. Trim whitespace/BOM and strip any trailing garbage before parsing
  4. Verify you are not passing a whole file/stream where a single message document is expected

Example fix

// before
var msg = parser.Parse<MyMessage>(jsonArrayText); // [ {...}, {...} ] -> fails
// after
var array = JArray.Parse(jsonArrayText);
var msg = array.Select(t => parser.Parse<MyMessage>(t.ToString())).ToList();
Defensive patterns

Strategy: try-catch

Validate before calling

static bool IsSingleJsonObject(string json) { json = json.Trim(); return json.StartsWith("{") && json.EndsWith("}") && json.LastIndexOf('}') == json.Length - 1; }

Try / catch

try { return parser.Parse<T>(json); } catch (InvalidProtocolBufferException ex) when (ex.Message == "Expected end of JSON after object") { log.Error("JSON has trailing content after the message object"); throw new BadRequestException("JSON must contain exactly one message object", ex); }

Prevention

When it happens

Trigger: Calling JsonParser.Parse<T>(string) or JsonParser.Merge with JSON containing trailing content after the closing brace — e.g. a JSON array of messages passed where one message is expected, concatenated JSON objects, trailing commas/garbage, or feeding a multi-document stream.

Common situations: Parsing a JSON array of messages one element at a time without extracting elements first; logs containing multiple concatenated JSON objects; copy-paste errors leaving trailing text; using a TextReader positioned over a stream with more data than one message.

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/ff7b5ba783be9e83. Report an issue: GitHub.

Appendix: source

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

        /// <param name="json">The JSON to parse.</param>
        internal void Merge(IMessage message, string json)
        {
            Merge(message, new StringReader(json));
        }

        /// <summary>
        /// Parses JSON read from <paramref name="jsonReader"/> and merges the information into the given message.
        /// </summary>
        /// <param name="message">The message to merge the JSON information into.</param>
        /// <param name="jsonReader">Reader providing the JSON to parse.</param>
        internal void Merge(IMessage message, TextReader jsonReader)
        {
            var tokenizer = JsonTokenizer.FromTextReader(jsonReader);
            Merge(message, tokenizer);
            var lastToken = tokenizer.Next();
            if (lastToken != JsonToken.EndDocument)
            {
                throw new InvalidProtocolBufferException("Expected end of JSON after object");
            }
        }

        /// <summary>
        /// Merges the given message using data from the given tokenizer. In most cases, the next
        /// token should be a "start object" token, but wrapper types and nullity can invalidate
        /// that assumption. This is implemented as an LL(1) recursive descent parser over the stream
        /// of tokens provided by the tokenizer. This token stream is assumed to be valid JSON, with the
        /// tokenizer performing that validation - but not every token stream is valid "protobuf JSON".
        /// </summary>
        private void Merge(IMessage message, JsonTokenizer tokenizer)
        {
            if (tokenizer.ObjectDepth > settings.RecursionLimit)
            {
                throw InvalidProtocolBufferException.JsonRecursionLimitExceeded();
            }
            if (message.Descriptor.IsWellKnownType)
            {

View on GitHub (pinned to 016f98412e)