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
- Ensure the JSON string contains exactly one top-level object with no trailing content
- 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
- Trim whitespace/BOM and strip any trailing garbage before parsing
- 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
- Trim trailing whitespace/BOM and garbage before parsing
- Split multi-message JSON arrays into individual elements before parsing
- Never pipe a whole multi-document stream into a single Parse call
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
- Expected an object
- Unexpected token type
- Multiple values specified for oneof
- Unknown field:
- Repeated field value was not an array. Token type:
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)