Protobufs Timestamp 作为 RFC 3339 字符串

Protobufs Timestamp as RFC 3339 string

提问人:Campbell 提问时间:5/4/2023 最后编辑:Campbell 更新时间:5/10/2023 访问量:791

问:

我目前有两个应用程序,它们使用 REST API 相互通信。第一个是 .NET 7 Web API 项目,第二个是使用 HTTP 客户端连接到 API 的 Java 桌面应用。

在当前的实现中,我被迫将所有REST模型编写为Java类和C#类,这有点麻烦。

我已经对 Protobufs 进行了一些研究,我相信我可以利用它的编译器为我创建这个类。我没有编写特定于语言的类,而是编写一个文件并编译它以创建类。值得重申的是,这两个应用程序都没有使用 gRPC,而只是使用 REST。.proto

我的解决方案中的一个文件允许用户搜索指定日期范围内的事件:

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

以前,这是通过以 RFC 3339 格式(例如)将两个日期传递给 API 来工作的,但是现在我已经引入了 Protobufs,我的 API 希望日期采用以下格式:2020-12-09T16:09:53Z

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

我想继续将日期作为 RFC 3339 字符串传递,而不是 /。secondsnanos

我试图向 C# 项目添加一个,但它在日期被包装在一组额外的引号中(即 )方面存在一些问题,并且它只“修复”了返回类型,如果我以 RFC 3339 格式传递日期,它们最终会成为 .JsonConverter"\"2020-12-09T16:09:53Z\""null

此外,Protobufs 文档中还指出,映射到 JSON 时使用的 RFC 3339 格式是预期的行为:

在 JSON 格式中,Timestamp 类型编码为 RFC 3339 格式的字符串。

裁判:

正因为如此,我不太确定写一个是正确的方法。JsonConverter

有没有办法让 Protobuf Timestamps 像我想要的那样工作,或者是否有 Timestamp 或 Protobuf 本身的替代方案可以解决在语言之间共享 REST API 模型的问题

C# .NET 日期时间 协议缓冲区

评论

0赞 DazWilkin 5/4/2023
根据我从您的问题中了解到的情况,我鼓励您重新考虑使用 Protobufs。
0赞 DazWilkin 5/4/2023
首先,如果你使用的是REST,你可能需要考虑OpenAPI。这是一个工具,允许你从你定义的表示 REST API 的架构生成 .NET、Java 和许多其他语言绑定。它还将生成 API 文档。
0赞 DazWilkin 5/4/2023
其次,您似乎只想使用 Protobufs 来生成代码。最佳做法是,不应在应用程序代码的存根中使用 Protobuf 生成的类型,而应(继续使用)特定于应用程序的类,并将这些类映射到 Protobuf 生成的“存根”。
0赞 DazWilkin 5/4/2023
我在自己的开发中使用了 gRPC 和 protobufs,并且是这些技术的支持者,但是,似乎通过尝试以这种方式采用 protobufs,您正在徒劳地增加复杂性(和问题)。
0赞 Campbell 5/4/2023
@DazWilkin感谢您抽出宝贵时间回复。我将研究 OpenAPI,我选择了 Protobufs,因为它集成到我的项目中没有太多麻烦(直到我遇到 JSON 序列化问题),所以这似乎是一个简单的解决方案,但如果有更好的东西可用,我不介意切换。

答:

2赞 Jon Skeet 5/4/2023 #1

听起来你只是将默认的 JSON 序列化与 System.Text.Json 或 Newtonsoft.Json 一起使用。我建议你不要对 protobuf 消息这样做,这些消息有自己的 JSON 表示形式。

相反,假设您使用的是 Google.Protobuf,只需调用消息即可获得 JSON 表示形式,或者使用 JsonFormatter - 您可以根据所需的任何格式选项创建不同的实例。ToString()JsonFormatter

同样,在解析时,只需使用 JsonParser - 或 on 的方法,例如 .ParseJsonMessageParser<T>SearchParameters.Parser.ParseJson(json)

评论

0赞 Campbell 5/4/2023
This is likely correct, I am using the built in Model Binding [learn.microsoft.com/en-us/aspnet/core/mvc/models/… to convert the JSON request into the Model created by Protobuf, and then returning an object from the controller method and letting .NET convert it (presumably using System.Text.Json). Does this mean that I have to manually parse the request/response in every controller method? Or is there some way to get the built in Model Binder to use the JsonFormatter/JsonParser
0赞 Jon Skeet 5/4/2023
@Campbell: That I don't know, I'm afraid. I'd expect there to be some way to configure the model binder, but it's not something I've done. Remember that the generated C# code uses partial classes, so you should be able to use or in a handwritten partial class to specify the binder to use.ModelBinderAttributeIModelBinderProvider
0赞 Campbell 5/10/2023 #2

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

Using a 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 in my API.TypeConverterTimestamp

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

评论

0赞 Jon Skeet 5/10/2023
You should expect to run into more problems if you're just special-casing Timestamp. There may be various other places where the Protobuf JSON representation doesn't behave in exactly the same way as Newtonsoft.Json or System.Text.Json. If "the other end" is either serializing or deserializing from protos, you should really use the same mechanism in your code, or you'll end up with hard-to-debug issues.