XINCGer/Unity3DTraining · error · ArgumentException
String contains high surrogate not preceded by low surrogate
Error message
String contains high surrogate not preceded by low surrogate
What it means
JsonFormatter's WriteString found a low surrogate (second half of a UTF-16 surrogate pair) without a preceding high surrogate. A lone low surrogate is not a valid Unicode character, so the formatter refuses to emit it into JSON and throws ArgumentException.
Solutions
- Validate string fields contain well-formed UTF-16 before assigning them to the message
- Re-decode the source bytes with the correct encoding (usually UTF-8) instead of treating raw bytes as UTF-16
- Sanitize/strip invalid surrogate chars before formatting
- Catch ArgumentException at the serialization boundary and quarantine the bad record
Example fix
// before
msg.Name = new string(chars, offset, len); // offset may start on a low surrogate
// after
static bool WellFormed(string s) { for (int i = 0; i < s.Length; i++) if (char.IsSurrogate(s[i]) && (i + 1 >= s.Length || !char.IsSurrogatePair(s[i], s[i+1]))) return false; return true; }
if (WellFormed(text)) msg.Name = text; Defensive patterns
Strategy: validation
Validate before calling
static bool IsWellFormedUtf16(string s) { for (int i = 0; i < s.Length; i++) { if (char.IsHighSurrogate(s[i])) { if (i + 1 >= s.Length || !char.IsLowSurrogate(s[i + 1])) return false; i++; } else if (char.IsLowSurrogate(s[i])) return false; } return true; } Type guard
static bool HasLoneLowSurrogate(string s) { for (int i = 0; i < s.Length; i++) if (char.IsLowSurrogate(s[i]) && (i == 0 || !char.IsHighSurrogate(s[i - 1]))) return true; return false; } Try / catch
try { return formatter.Format(message); } catch (ArgumentException ex) when (ex.Message.Contains("high surrogate")) { log.Warn("Lone low surrogate in string field"); return null; } Prevention
- Sanitize strings from untrusted sources before assigning to proto fields
- Avoid manual char-buffer slicing of strings
- Round-trip check via Encoding.UTF8 with exception throwing to catch invalid data early
When it happens
Trigger: Formatting a message whose string field starts with or contains a lone low surrogate (U+DC00–U+DFFF) not preceded by a high surrogate — caused by malformed decoded data, byte truncation, or manual string construction from raw char arrays.
Common situations: Decoding binary/network payloads with incorrect encodings; concatenating or slicing strings at odd offsets; receiving corrupted data from external systems; storing arbitrary bytes in string fields.
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
- String contains low surrogate not followed by high surrogate
- Unhandled dictionary key type:
- Offset must be within the buffer
- Length must be non-negative and within the buffer
- Size limit must be positive
AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12).
Data as JSON: /api/errors/aeaf33b59d98a02c.
Report an issue: GitHub.
Appendix: source
Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonFormatter.cs:710
writer.Write(CommonRepresentations[c]);
continue;
}
if (char.IsHighSurrogate(c))
{
// Encountered first part of a surrogate pair.
// Check that we have the whole pair, and encode both parts as hex.
i++;
if (i == text.Length || !char.IsLowSurrogate(text[i]))
{
throw new ArgumentException("String contains low surrogate not followed by high surrogate");
}
HexEncodeUtf16CodeUnit(writer, c);
HexEncodeUtf16CodeUnit(writer, text[i]);
continue;
}
else if (char.IsLowSurrogate(c))
{
throw new ArgumentException("String contains high surrogate not preceded by low surrogate");
}
switch ((uint)c)
{
// These are not required by json spec
// but used to prevent security bugs in javascript.
case 0xfeff: // Zero width no-break space
case 0xfff9: // Interlinear annotation anchor
case 0xfffa: // Interlinear annotation separator
case 0xfffb: // Interlinear annotation terminator
case 0x00ad: // Soft-hyphen
case 0x06dd: // Arabic end of ayah
case 0x070f: // Syriac abbreviation mark
case 0x17b4: // Khmer vowel inherent Aq
case 0x17b5: // Khmer vowel inherent Aa
HexEncodeUtf16CodeUnit(writer, c);
break;
View on GitHub (pinned to 016f98412e)