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

  1. Convert the epoch value to an RFC 3339 UTC string before parsing, e.g. DateTimeOffset.FromUnixTimeSeconds(sec).UtcDateTime.ToString("o")
  2. Emit the timestamp as 'YYYY-MM-DDTHH:mm:ss[.fff]Z' (RFC 3339) in the JSON
  3. If the field is truly a numeric epoch, change the proto field to int64/int32 instead of Timestamp
  4. 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

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


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)