XINCGer/Unity3DTraining · error · InvalidProtocolBufferException
Expected string value for FieldMask
Error message
Expected string value for FieldMask
What it means
MergeFieldMask requires a google.protobuf.FieldMask to appear in JSON as a comma-separated string of field paths (e.g. "user.displayName,photo"). If the JSON token is not a string value (object, array, number, null), the parser throws 'Expected string value for FieldMask'.
Solutions
- Send the FieldMask as a single comma-separated string: "user.displayName,photo".
- Convert arrays of paths to string.Join(",", paths) before serializing.
- Catch InvalidProtocolBufferException and map array-form masks to the string form before parsing.
- Follow the canonical protobuf JSON mapping documented for google.protobuf.FieldMask.
Example fix
// before
string json = "{\"mask\": [\"user\", \"photo\"]}";
// after
string json = "{\"mask\": \"user,photo\"}"; Defensive patterns
Strategy: type-guard
Validate before calling
// Convert array-form field masks to canonical string form before parsing
string ToFieldMaskJson(IEnumerable<string> paths) =>
"\"" + string.Join(",", paths) + "\""; Type guard
bool IsFieldMaskString(object v) => v is string s && s.Split(',').All(p => p.Length > 0); Try / catch
try { var msg = JsonParser.Default.Parse<T>(json); }
catch (InvalidProtocolBufferException ex) when (ex.Message == "Expected string value for FieldMask")
{ log.Warn("FieldMask must be a comma-separated string"); throw new ArgumentException("fieldMask must be a string like \"user,photo\"", ex); } Prevention
- Always send FieldMask as one comma-separated string, not an array/object
- Convert client-side path arrays with string.Join(",", paths)
- Document the canonical FieldMask JSON form in your API docs
- Reject array-form masks at your ingress with a clear 400 message
When it happens
Trigger: Parsing JSON where a FieldMask field is given as an array (["user"]) or an object ({"paths": [...]}) instead of the canonical string form "user,photo" via JsonParser.Parse<T>.
Common situations: Clients sending FieldMask as JSON arrays (a common intuition); JSON from non-protobuf serializers; API consumers unfamiliar with the protobuf JSON mapping for well-known types.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Expected an object
- Unsupported JSON token type
- Unsupported conversion from JSON number for field type
- Expected string value for Duration
- Invalid field mask
AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12).
Data as JSON: /api/errors/9b00888f62b98945.
Report an issue: GitHub.
Appendix: source
Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs:907
}
if (!Duration.IsNormalized(seconds, nanos))
{
throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
}
message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.SetValue(message, seconds);
message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.SetValue(message, nanos);
}
catch (FormatException)
{
throw new InvalidProtocolBufferException("Invalid Duration value: " + token.StringValue);
}
}
private static void MergeFieldMask(IMessage message, JsonToken token)
{
if (token.Type != JsonToken.TokenType.StringValue)
{
throw new InvalidProtocolBufferException("Expected string value for FieldMask");
}
// TODO: Do we *want* to remove empty entries? Probably okay to treat "" as "no paths", but "foo,,bar"?
string[] jsonPaths = token.StringValue.Split(FieldMaskPathSeparators, StringSplitOptions.RemoveEmptyEntries);
IList messagePaths = (IList)message.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(message);
foreach (var path in jsonPaths)
{
messagePaths.Add(ToSnakeCase(path));
}
}
// Ported from src/google/protobuf/util/internal/utility.cc
private static string ToSnakeCase(string text)
{
var builder = new StringBuilder(text.Length * 2);
// Note: this is probably unnecessary now, but currently retained to be as close as possible to the
// C++, whilst still throwing an exception on underscores.
bool wasNotUnderscore = false; // Initialize to false for case 1 (below)
bool wasNotCap = false;View on GitHub (pinned to 016f98412e)