#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