Newtonsoft C # — пользовательское преобразование даты и времени в Google.Protobuf.WellKnownTypes.Временная метка

#c# #json.net

#c# #json.net

Вопрос:

Мой сценарий таков: я должен протестировать вызовы GRPC. Я должен получить тело JSON и превратиться в протообъект. Когда атрибутами являются int32, string и т. Д., Все работает отлично. Но когда тип является отметкой времени, тогда возникает проблема.

Я написал этот код в 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);
    }
}
 

Вывод:
"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.

Я также пытался реализовать пользовательский конвертер TimeStampConverter , но безуспешно.

Что я делаю не так?

Комментарии:

1. «Я должен получить тело JSON и превратиться в прото-объект» — если это сообщение protobuf, просто проанализируйте его с помощью встроенного анализатора JSON. К сожалению, мы не видим здесь большого контекста, поэтому трудно дать больше советов, чем это. Но что-то вроде var message = MyMessageType.Parser.ParseJson(json); в принципе.

2. @JonSkeet извините, я был так сосредоточен, пытаясь найти решение Newtonsoft, что не понял, что protobuf objects имеет встроенный анализатор. Это намного проще, чем пытаться создать конвертер. Я тестировал здесь, это сработало. Большое спасибо!

Ответ №1:

Это работает:

  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());
            }
        }
    }
 

Затем, чтобы использовать его, вы делаете вот так:

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

Ответ №2:

Похоже, вы немного нарушаете последовательность. Я считаю, что самый простой способ — предоставить преобразователь контрактов вместо преобразователя в JsonConvert.Deserialize<>() . В качестве примера с расстояния в тысячу футов:

 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;
    }
}
 

Этот способ, как правило, более эффективен, поскольку вы можете видеть любой тип свойства или имя, которое обычно охватывает большинство моих баз.

Комментарии:

1. Привет @Matt, спасибо за ответ! К сожалению, этот код не работает, потому что для второго параметра требуется конвертер, а не сериализатор, как вы можете видеть в этом скрипаче: dotnetfiddle.net/0w0fzi