XINCGer/Unity3DTraining · error · InvalidProtocolBufferException
Invalid field mask
Error message
Invalid field mask: {text} What it means
Google.Protobuf's JsonParser throws this InvalidProtocolBufferException when converting a FieldMask from its JSON snake_case representation back to proto field paths. In a field mask, an underscore must always be followed by an ASCII letter that becomes the capitalized character of the camelCase proto field name. An underscore at the end of the string, or followed by anything other than a letter, cannot be mapped to a valid proto path, so the parser rejects it.
Solutions
- Fix the JSON input: each '_' in a field mask must be immediately followed by a letter (the snake_case-to-camelCase encoding), e.g. "user.profileName", never "user__id" or "user_."
- Remove trailing underscores from field mask strings before sending them to ParseJson.
- If generating field masks programmatically, use a canonical encoder (e.g. FieldMask.ToString / the WellKnownTypes FieldMask helpers) instead of hand-building strings.
- If you control the producer, validate field masks against the target message's field names before serialization.
Example fix
// before
var mask = json["updateMask"].ToString(); // "user__email"
var parsed = JsonParser.Default.Parse<UpdateRequest>(json); // throws
// after
string mask = json["updateMask"].ToString().Replace("__", "_").TrimEnd('_'); // "user_email"
var parsed = JsonParser.Default.Parse<UpdateRequest>(json); Defensive patterns
Strategy: validation
Validate before calling
static bool IsValidFieldMaskJson(string mask) =>
!string.IsNullOrEmpty(mask) && !mask.EndsWith("_") && !mask.Contains("__");
// check each '_' is followed by [A-Za-z]
if (!IsValidFieldMaskJson(mask)) throw new FormatException("Invalid field mask: " + mask); Type guard
static bool IsValidFieldMask(string mask)
{
for (int i = 0; i < mask.Length; i++)
if (mask[i] == '_' && (i + 1 >= mask.Length || !char.IsAsciiLetter(mask[i + 1])))
return false;
return true;
} Try / catch
try
{
var msg = JsonParser.Default.Parse<TRequest>(json);
}
catch (InvalidProtocolBufferException ex) when (ex.Message.Contains("Invalid field mask"))
{
// log/repair the field mask string and retry
} Prevention
- Always produce field masks via a canonical encoder, never by string concatenation
- Trim trailing separators from user-supplied field mask input
- Test field mask parsing round-trips (FieldMask -> JSON -> parser) in unit tests
When it happens
Trigger: Calling JsonParser.ParseJson (or any descriptor-driven FromJson) on JSON containing a FieldMask value with a trailing underscore (e.g. "user._"), a double underscore ("user__id"), or an underscore followed by a non-letter character. The throw site is in the field-mask path conversion loop at JsonParser.cs:959, reached whenever a field of type google.protobuf.FieldMask is parsed from JSON.
Common situations: Hand-written JSON where a field mask was typed with snake_case separators instead of valid camelCase-with-single-underscore encoding; code that built a field mask string by string concatenation leaving a trailing '_'; interoperating with a service that emits non-canonical field masks; migrating from a client library with laxer field-mask validation.
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
- Expected string value for FieldMask
- Offset must be within the buffer
- Length must be non-negative and within the buffer
- Stream.Read returned a negative count
- SpaceLeft can only be called on CodedOutputStreams that are…
AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12).
Data as JSON: /api/errors/69181d92cfa2a6e2.
Report an issue: GitHub.
Appendix: source
Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs:959
if (wasNotUnderscore && // case 1 out
(wasNotCap || // case 2 in, case 3 out
(i + 1 < text.Length && // case 3 out
(text[i + 1] >= 'a' && text[i + 1] <= 'z')))) // ascii_islower(text[i + 1])
{ // case 4 in
// We add an underscore for case 2 and case 4.
builder.Append('_');
}
// ascii_tolower, but we already know that c *is* an upper case ASCII character...
builder.Append((char)(c + 'a' - 'A'));
wasNotUnderscore = true;
wasNotCap = false;
}
else
{
builder.Append(c);
if (c == '_')
{
throw new InvalidProtocolBufferException("Invalid field mask: " + text);
}
wasNotUnderscore = true;
wasNotCap = true;
}
}
return builder.ToString();
}
#endregion
/// <summary>
/// Settings controlling JSON parsing.
/// </summary>
public sealed class Settings
{
/// <summary>
/// Default settings, as used by <see cref="JsonParser.Default"/>. This has the same default
/// recursion limit as <see cref="CodedInputStream"/>, and an empty type registry.
/// </summary>View on GitHub (pinned to 016f98412e)