Waldrich
Waldrich

Reputation: 25

Newtonsoft C# - Custom DateTime conversion to Google.Protobuf.WellKnownTypes.Timestamp

My scenario is: I have to test GRPC calls. I have to get a JSON body and turn into a Proto object. When attributes are int32, string, etc it works perfectly fine. But when the type is TimeStamp, then the problem happens.

I wrote this code in Fiddler https://dotnetfiddle.net/H1U3i4:

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
                    
public class Program
{
    public class MyProtobufObject
    {
        public Google.Protobuf.WellKnownTypes.Timestamp openingDatetime {get;set;}
    }
    
    public class TimeStampConverter : DateTimeConverterBase
    {
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            DateTime date = DateTime.Parse(reader.Value.ToString());
            return Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(date).ToString();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue(((Google.Protobuf.WellKnownTypes.Timestamp)value).ToString());
        }
    }
    
    
    public static void Main()
    {
        string sDate = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow).ToString();
        Console.WriteLine(sDate);
        string myJsonBodyRequest = "{\"openingDatetime\":"+sDate+"}";
        Console.WriteLine(myJsonBodyRequest);
        MyProtobufObject myObjectWithConverter = JsonConvert.DeserializeObject<MyProtobufObject>(myJsonBodyRequest, new TimeStampConverter());
        MyProtobufObject myObjectWithoutConverter = JsonConvert.DeserializeObject<MyProtobufObject>(myJsonBodyRequest);
    }
}

Output is: "2021-02-24T17:28:52.391136Z"

{"openingDatetime":"2021-02-24T17:28:52.391136Z"}

Unhandled exception. Newtonsoft.Json.JsonSerializationException: Error converting value 02/24/2021 17:28:52 to type 'Google.Protobuf.WellKnownTypes.Timestamp'. Path 'openingDatetime', line 1, position 48. ---> System.ArgumentException: Could not cast or convert from System.DateTime to Google.Protobuf.WellKnownTypes.Timestamp.

I also tried to implement a custom converter TimeStampConverter but no success.

What am I doing wrong?

Upvotes: 1

Views: 3384

Answers (2)

user443239
user443239

Reputation: 46

This works:

 public class TimeStampContractResolver : DefaultContractResolver
    {
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);

            if (property.PropertyType == typeof(Google.Protobuf.WellKnownTypes.Timestamp))
            {
                property.Converter = new TimeStampConverter();
            }

            return property;
        }

        public class TimeStampConverter : DateTimeConverterBase
        {
            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                DateTime date = DateTime.Parse(reader.Value.ToString());
                date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
                return Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(date);
            }

            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                writer.WriteValue(((Google.Protobuf.WellKnownTypes.Timestamp)value).ToString());
            }
        }
    }

Then to use it you do like this:

 var settings = new JsonSerializerSettings
 {
     ContractResolver = new TimeStampContractResolver()
 };
 var myObj = JsonConvert.DeserializeObject<MyObject>(jsonString, settings);

Upvotes: 2

Matt Bourque
Matt Bourque

Reputation: 73

It looks like you're going a little out of sequence. I find the easiest way is to provide a contract resolver instead of a converter to the JsonConvert.Deserialize<>(). As an example from a thousand feet:

var result = JsonConvert.DeserializeObject<Target>(source, new JsonSerializer()
{
    ContractResolver = new YourTimestampContractResolver(),
};

// elsewhere... Inherting the DefaultContractResolver saves you some implementation
public class YourTimestampContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);

        if (property.PropertyType == typeof(DateTime))
        {
            property.Converter = new YourCustomDateTimeConverter();
        }

        return property;
    }
}

This way is generally way more powerful as you can see any property type or name, which has generally covered most of my bases.

Upvotes: 0

Related Questions