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

  1. Validate string fields contain well-formed UTF-16 before assigning them to the message
  2. Re-decode the source bytes with the correct encoding (usually UTF-8) instead of treating raw bytes as UTF-16
  3. Sanitize/strip invalid surrogate chars before formatting
  4. 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

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


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)