XINCGer/Unity3DTraining · error · InvalidProtocolBufferException
Expected string value for Timestamp
Error message
Expected string value for Timestamp
What it means
The protobuf JSON mapping represents google.protobuf.Timestamp exclusively as an RFC 3339 string (e.g. '2024-01-15T10:30:00Z'). MergeTimestamp first checks that the incoming JSON token is a string; if a Timestamp field receives a number, object, or other token type, it throws InvalidProtocolBufferException('Expected string value for Timestamp').
Solutions
- Convert the epoch value to an RFC 3339 UTC string before parsing, e.g. DateTimeOffset.FromUnixTimeSeconds(sec).UtcDateTime.ToString("o")
- Emit the timestamp as 'YYYY-MM-DDTHH:mm:ss[.fff]Z' (RFC 3339) in the JSON
- If the field is truly a numeric epoch, change the proto field to int64/int32 instead of Timestamp
- Fix the JSON producer's serializer settings to render DateTime/DateTimeOffset as ISO 8601 strings
Example fix
// before
var json = "{\"createdAt\": " + unixSeconds + "}"; // number for Timestamp
// after
var iso = DateTimeOffset.FromUnixTimeSeconds(unixSeconds).UtcDateTime.ToString("o");
var json = "{\"createdAt\": \"" + iso + "\"}"; // "2024-01-15T10:30:00Z" Defensive patterns
Strategy: validation
Validate before calling
bool IsTimestampJsonString(object token) => token is string s &&
System.Text.RegularExpressions.Regex.IsMatch(s,
@"^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$"); Type guard
bool IsStringToken(object token) => token is string;
Try / catch
try { return JsonParser.Default.Parse<T>(json); }
catch (InvalidProtocolBufferException ex) when (ex.Message.Contains("Expected string value for Timestamp")) {
logger.LogWarning(ex, "Timestamp sent as non-string (e.g. epoch number)");
return null;
} Prevention
- Configure serializers to render dates as ISO 8601/RFC 3339 strings
- Never feed epoch numbers into google.protobuf.Timestamp fields
- Convert Unix time with DateTimeOffset.FromUnixTimeSeconds(...).UtcDateTime.ToString("o")
When it happens
Trigger: JsonParser.Parse<T> on messages containing google.protobuf.Timestamp fields where the JSON value is a number (Unix epoch seconds/millis) or an object ({"seconds":...}), e.g. {"createdAt": 1705314600}.
Common situations: APIs mixing Unix timestamps with proto JSON; JavaScript Date objects serialized as numbers by JSON.stringify alternatives; hand-written JSON assuming epoch-style timestamps; migration from another serializer's convention.
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
- Invalid Timestamp value
- Invalid field type
- Unable to format value of type
- Type registry has no descriptor for type name '
- Struct fields cannot have an empty key or a null value.
AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12).
Data as JSON: /api/errors/b4d775bfb3b7109c.
Report an issue: GitHub.
Appendix: source
Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/JsonParser.cs:787
/// Checks that any infinite/NaN values originated from the correct text.
/// This corrects the lenient whitespace handling of double.Parse/float.Parse, as well as the
/// way that Mono parses out-of-range values as infinity.
/// </summary>
private static void ValidateInfinityAndNan(string text, bool isPositiveInfinity, bool isNegativeInfinity, bool isNaN)
{
if ((isPositiveInfinity && text != "Infinity") ||
(isNegativeInfinity && text != "-Infinity") ||
(isNaN && text != "NaN"))
{
throw new InvalidProtocolBufferException("Invalid numeric value: " + text);
}
}
private static void MergeTimestamp(IMessage message, JsonToken token)
{
if (token.Type != JsonToken.TokenType.StringValue)
{
throw new InvalidProtocolBufferException("Expected string value for Timestamp");
}
var match = TimestampRegex.Match(token.StringValue);
if (!match.Success)
{
throw new InvalidProtocolBufferException("Invalid Timestamp value: " + token.StringValue);
}
var dateTime = match.Groups["datetime"].Value;
var subseconds = match.Groups["subseconds"].Value;
var offset = match.Groups["offset"].Value;
try
{
DateTime parsed = DateTime.ParseExact(
dateTime,
"yyyy-MM-dd'T'HH:mm:ss",
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
// TODO: It would be nice not to have to create all these objects... easy to optimize later though.View on GitHub (pinned to 016f98412e)