Search code examples
c#.netdatetimeprotocol-buffers

Protobufs Timestamp as RFC 3339 string


I currently have two applications, which communicate with each other using a REST API. The first is a .NET 7 Web API project and the second is a Java desktop app which uses an HTTP Client to connect to the API.

In the current implementation I am forced to write all of the REST models as both Java classes and C# classes, which is a little cumbersome.

I have done some looking into Protobufs, and I believe that I can leverage its compiler to do this class creation for me. Instead of writing language specific classes I would instead write a .proto file and compile it to create the classes. It's worth reiterating that neither of the applications use gRPC, just REST.

One file in my solution allows a user to search for events within a specified date range:

message SearchParameters {
    google.protobuf.Timestamp date_range_start = 1;
    google.protobuf.Timestamp date_range_end = 2;
}

Previously this worked by passing two dates to the API in RFC 3339 format (e.g. 2020-12-09T16:09:53Z), however now that I have introduced Protobufs my API is expecting the dates to be in the format:

{
  "seconds": 11223344,
  "nanos": 0
}

I would like to continue to pass dates in as RFC 3339 strings, as opposed to to seconds/nanos.

I have tried to add a JsonConverter to the C# project, but it had some issues with the dates getting wrapped in an extra set of quotes (i.e. "\"2020-12-09T16:09:53Z\""), and it only "fixed" the return types, if I passed dates in RFC 3339 format they would end up as null.

Additionally there are indications in the Protobufs documentation that the expected behaviour is for RFC 3339 format to be used when mapping to JSON:

In JSON format, the Timestamp type is encoded as a string in the RFC 3339 format.

Refs:

Because of this I am not really sure that writing a JsonConverter is the correct approach.

Is there some way to make Protobuf Timestampss work like I want, or is there perhaps an alternative to either Timestamps, or Protobuf itself that would solve the issue of sharing REST API models between languages


Solution

  • In the end I was able to solve my issue by creating a TypeConverter, to convert the RFC 3339 string to a Timestamp.

    Using a TypeConverter was much more versatile than a custom Model Binder, as it only needed to be defined once, rather than manually specifying the custom model binder any time I used a Timestamp in my API.

    TypeDescriptor.AddAttributes(typeof(Timestamp), new TypeConverterAttribute(typeof(TimestampTypeConverter)));
    
    public class TimestampTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext? context, System.Type sourceType)
        {
            return sourceType == typeof(string);
        }
    
        public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
        {
            return Timestamp.FromDateTime(DateTime.Parse(value.ToString()!, null, DateTimeStyles.RoundtripKind));
        }
    }