本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

暂无数据

protobuf-net 序列化/反序列化 DateTime 和 Guid 类型

发布于2021-11-23 09:12     阅读(904)     评论(0)     点赞(14)     收藏(5)


我在protobuf-net获取TimeDateGuid类型的值时遇到了一些问题

我有.Net Client.Net Server,它们通过protobuf-net进行通信现在我必须实现从 java 客户端到这个.Net 服务器的通信,我不能改变已经存在的.Net 服务器通信逻辑,所以我必须使用已经存在的 protobuf 通信,问题如下:

protobuf-net理解两种.net类型:DateTimeGuid,但我无法通过google protobuf解析它

.Net 服务器类示例:

[ProtoContract]
public class SomeClass
{
[ProtoMember(1)]
public DateTime? CurrentDateTime { get; set; }

[ProtoMember(2)]
public Guid CurrentGiud { get; set; }
}

我无法通过google protobuf解析它,因为它对DateTimeGuid类型一无所知,所以我只能从这些字段中获取字节 [] ,.proto示例:

message SomeClass
{
 bytes CurrentDateTime = 1;
 bytes CurrentGiud = 2;
}

所以在序列化/反序列化一个流之后,我可以从这些字段中获取byte[],现在我需要以某种方式将它转换为适当的值,所以我需要这样的东西:

var customDateTime = ConvertByteArrayToCustomDateTime(byteArray);
byte[] byteArray = ConvertCustomDateTimeToByteArray(customDateTime);

var customGuid = ConvertByteArrayToCustomGuid(byteArray);
byte[] byteArray = ConvertCustomGuidToByteArray(customGuid);

或这个:

string strDateTime = ConvertByteArrayToStringDateTime(byteArray); //e.g. "13.08.2019 17:42:31"
byte[] byteArray = ConvertStringDateTimeToByteArray(strDateTime);

string strGuid = ConvertByteArrayToStringGuid(byteArray); // e.g. "{7bb7cdac-ebad-4acf-90ff-a5525be3caac}"
byte[] byteArray = ConvertStringGuidToByteArray(strGuid);

DateTime 真实示例:

示例 N1:

DateTime = 13.08.2019 17:42:31
after serialization/deserialization
byte[] = { 8, 142, 218, 151, 213, 11, 16, 3 }

示例 N2:

DateTime = 25.06.2019 20:15:10
after serialization/deserialization
byte[] = { 8, 156, 131, 148, 209, 11, 16, 3 }

指导真实示例:

示例 N1:

Guid = {7bb7cdac-ebad-4acf-90ff-a5525be3caac}
after serialization/deserialization
byte[] = { 9, 172, 205, 183, 123, 173, 235, 207, 74, 17, 144, 255, 165, 82, 91, 227, 202, 172 }

示例 N2:

Guid = {900246bb-3a7b-44d4-9b2f-1da035ca51f4}
after serialization/deserialization
byte[] = { 9, 187, 70, 2, 144, 123, 58, 212, 68, 17, 155, 47, 29, 160, 53, 202, 81, 244 }

解决方案:

将以下消息添加到您的.proto 中

message CustomDateTime 
{
  sint64 value = 1; // the offset (in units of the selected scale) from 1970/01/01
  CustomTimeSpanScale scale = 2; // the scale of the timespan [default = DAYS]
  CustomDateTimeKind kind = 3; // the kind of date/time being represented [default = UNSPECIFIED]
  enum CustomTimeSpanScale 
  {
    DAYS = 0;
    HOURS = 1;
    MINUTES = 2;
    SECONDS = 3;
    MILLISECONDS = 4;
    TICKS = 5;

    MINMAX = 15; // dubious
  }
  enum CustomDateTimeKind
  {     
     // The time represented is not specified as either local time or Coordinated Universal Time (UTC).
     UNSPECIFIED = 0;
     // The time represented is UTC.
     UTC = 1;
     // The time represented is local time.
     LOCAL = 2;
   }
}

message CustomGuid 
{
  fixed64 lo = 1; // the first 8 bytes of the guid (note:crazy-endian)
  fixed64 hi = 2; // the second 8 bytes of the guid (note:crazy-endian)
}

现在你的.proto类应该是这样的:

message SomeClass
{
 CustomDateTime CurrentDateTime = 1;
 CustomGuid CurrentGiud = 2;
}

简单的日期时间解析器:

        public static string ConvertCustomDateTimeToString(CustomDateTime customDateTime)
        {
            var dateTime = DateTime.Parse("01.01.1970 00:00:00");

            if (customDateTime.Scale == CustomDateTime.Types.CustomTimeSpanScale.Seconds)
            {
                dateTime = dateTime.AddSeconds(customDateTime.Value);
            }
            else
            {
                throw new Exception("CustomDateTime supports only seconds");
            }

            return dateTime.ToString();
        }

        public static CustomDateTime ConvertStringToCustomDateTime(string strDateTime)
        {
            var defaultTime = DateTime.Parse("01.01.1970 00:00:00");
            var dateTime = DateTime.Parse(strDateTime);

            var customDateTime = new CustomDateTime
            {
                Kind = CustomDateTime.Types.CustomDateTimeKind.Unspecified,
                Scale = CustomDateTime.Types.CustomTimeSpanScale.Seconds,
                Value = (long) (dateTime - defaultTime).TotalSeconds
            };

            return customDateTime;
        }

简单的Guid解析器:

public static string ConvertCustomGuidToString(CustomGuid customGuid)
        {
            var str = string.Empty;

            var array = BitConverter.GetBytes(customGuid.Lo);

            var newArray = new byte[8];
            newArray[0] = array[3];
            newArray[1] = array[2];
            newArray[2] = array[1];
            newArray[3] = array[0];
            newArray[4] = array[5];
            newArray[5] = array[4];
            newArray[6] = array[7];
            newArray[7] = array[6];

            str += BitConverter.ToString(newArray).Replace("-", "");

            str += BitConverter.ToString(BitConverter.GetBytes(customGuid.Hi)).Replace("-", "");

            return str;
        }

        public static CustomGuid ConvertStringToCustomGuid(string strGuid)
        {
            strGuid = strGuid.Replace(" ", "");
            strGuid = strGuid.Replace("-", "");
            strGuid = strGuid.Replace("{", "");
            strGuid = strGuid.Replace("}", "");

            if (strGuid.Length != 32)
            {
                throw new Exception("Wrong Guid format");
            }

            byte[] array = new byte[16];

            for (int i = 0; i < 32; i += 2)
                array[i / 2] = Convert.ToByte(strGuid.Substring(i, 2), 16);

            var newArrayLo = new byte[8];
            newArrayLo[0] = array[3];
            newArrayLo[1] = array[2];
            newArrayLo[2] = array[1];
            newArrayLo[3] = array[0];
            newArrayLo[4] = array[5];
            newArrayLo[5] = array[4];
            newArrayLo[6] = array[7];
            newArrayLo[7] = array[6];

            var newArrayHi = new byte[8];
            newArrayHi[0] = array[8];
            newArrayHi[1] = array[9];
            newArrayHi[2] = array[10];
            newArrayHi[3] = array[11];
            newArrayHi[4] = array[12];
            newArrayHi[5] = array[13];
            newArrayHi[6] = array[14];
            newArrayHi[7] = array[15];

            var customGuid = new CustomGuid
            {
                Lo = BitConverter.ToUInt64(newArrayLo, 0),
                Hi = BitConverter.ToUInt64(newArrayHi, 0)
            };

            return customGuid;
        }

解决方案


We need to talk about the two types separately. There's back-story to each!


DateTime / TimeSpan - so: way back in history, .NET folks kept wanting protobuf-net to round-trip DateTime / TimeSpan. There was nothing defined by Google for this kind of purpose, so protobuf-net made something up. The details are in bcl.proto, but I don't recommend worrying about that. The short version would be: "they're kinda awkward to work with if you're not protobuf-net".

Roll forward 5+ years, and Google finally defined the well-known Duration and Timestamp types. Unfortunately, they're not 1:1 matches for how protobuf-net decided to implement them, and I can't change the default layout without breaking existing consumers. But! For new code, or for cross-platform purposes, protobuf-net knows how to talk Duration / Timestamp, and if it is even remotely possible, I strongly recommend changing your layout. The good news is: this is really simple:

[ProtoMember(1, DataFormat = DataFormat.WellKnown)]
public DateTime? CurrentDateTime { get; set; }

This will now use .google.protobuf.Timestamp instead of .bcl.DateTime; this also works with TimeSpan / .google.protobuf.Duration.

The key point here: there is a simple option that you can switch to that will make this "just work"; the default is for compatibility with something that protobuf-net had to invent before Google had decided on a layout.

Note that changing to DataFormat.WellKnown is a data breaking change; the layout is different. If there was a way to automatically detect and compensate, it already would; there isn't.


Guid - this should have been much simpler; the sensible idea here would have been to just to serialize it as bytes in .proto terms, but ... and I regret this, I did a stupid and tried to do something clever. It backfired and I regret it. What it does is ... kinda silly, although it does make sense internally. It accesses the Guid as two consecutive fixed64 fields (again, look in bcl.proto) where these are the low/high bytes in Microsoft's craz-endian layout. By crazy-endian, I mean where the guid 00112233-4455-6677-8899-AABBCCDDEEFF is represented by the bytes 33-22-11-00-55-44-77-66-88-99-AA-BB-CC-DD-EE-FF (emphasis: this bit isn't me; this is what Microsoft and .NET do internally with guids). So; to take your N1 example, the two half fragments you're seeing there are:

  • 0x09 = "field 1, type fixed64"
  • 0x AC-CD-B7-7B-AD-EB-CF-4A - first half in crazy-endian
  • 0x11 (decimal 17) = "field 2, type fixed64"
  • 0x 90-FF-A5-52-5B-E3-CA-AC - second half in crazy-endian

Frankly, for cross-platform work, I would suggest for Guid, expose it as string or byte[] instead, and accept my sincere apologies for the inconvenience!


In both cases: if you can't change the layout, look in bcl.proto for what is actually happening. If you use Serializer.GetProto<T>(), it should generate a schema that imports bcl.proto automatically.



所属网站分类: 技术文章 > 问答

作者:黑洞官方问答小能手

链接:http://www.javaheidong.com/blog/article/329908/c4dbb1e9dcbb6796ca29/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

14 0
收藏该文
已收藏

评论内容:(最多支持255个字符)