{"record":{"id":"ff7b5ba783be9e83","repo":"XINCGer/Unity3DTraining","slug":"expected-end-of-json-after-object","errorCode":null,"errorMessage":"Expected end of JSON after object","messagePattern":"Expected end of JSON after object","errorType":"validation","errorClass":"InvalidProtocolBufferException","httpStatus":null,"severity":"error","filePath":"NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs","lineNumber":138,"sourceCode":"        /// <param name=\"json\">The JSON to parse.</param>\n        internal void Merge(IMessage message, string json)\n        {\n            Merge(message, new StringReader(json));\n        }\n\n        /// <summary>\n        /// Parses JSON read from <paramref name=\"jsonReader\"/> and merges the information into the given message.\n        /// </summary>\n        /// <param name=\"message\">The message to merge the JSON information into.</param>\n        /// <param name=\"jsonReader\">Reader providing the JSON to parse.</param>\n        internal void Merge(IMessage message, TextReader jsonReader)\n        {\n            var tokenizer = JsonTokenizer.FromTextReader(jsonReader);\n            Merge(message, tokenizer);\n            var lastToken = tokenizer.Next();\n            if (lastToken != JsonToken.EndDocument)\n            {\n                throw new InvalidProtocolBufferException(\"Expected end of JSON after object\");\n            }\n        }\n\n        /// <summary>\n        /// Merges the given message using data from the given tokenizer. In most cases, the next\n        /// token should be a \"start object\" token, but wrapper types and nullity can invalidate\n        /// that assumption. This is implemented as an LL(1) recursive descent parser over the stream\n        /// of tokens provided by the tokenizer. This token stream is assumed to be valid JSON, with the\n        /// tokenizer performing that validation - but not every token stream is valid \"protobuf JSON\".\n        /// </summary>\n        private void Merge(IMessage message, JsonTokenizer tokenizer)\n        {\n            if (tokenizer.ObjectDepth > settings.RecursionLimit)\n            {\n                throw InvalidProtocolBufferException.JsonRecursionLimitExceeded();\n            }\n            if (message.Descriptor.IsWellKnownType)\n            {","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/XINCGer/Unity3DTraining/blob/016f98412ea5e11756b286736f479b66ab6216d5/NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs#L120-L156","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nvar msg = parser.Parse<MyMessage>(jsonArrayText); // [ {...}, {...} ] -> fails\n// after\nvar array = JArray.Parse(jsonArrayText);\nvar msg = array.Select(t => parser.Parse<MyMessage>(t.ToString())).ToList();","handlingStrategy":"try-catch","validationCode":"static bool IsSingleJsonObject(string json) { json = json.Trim(); return json.StartsWith(\"{\") && json.EndsWith(\"}\") && json.LastIndexOf('}') == json.Length - 1; }","typeGuard":null,"tryCatchPattern":"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); }","preventionTips":["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"],"tags":["protobuf","json-parsing","trailing-content","invalid-json"],"backgroundTag":"protobuf-unmarshal-failed","analyzedSha":"016f98412ea5e11756b286736f479b66ab6216d5","analyzedAt":"2026-09-12T01:08:58.711Z","contentChangedAt":"2026-09-12T01:08:58.711Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}