C#中用 protobuf Struct 实现弱类型的json 序列化/反序列化

1 篇文章 0 订阅

目标

    在.net 大力支持使用 gRPC 的背景下,通过 jsontranscoding 可以实现 gRPC/WebAPI 一鱼两吃。有时候不想把json对象的所有属性都在 proto 中定义出来,比如设备对象,不同设备有不同的属性,要都强类型那C#里面的对象属性会很多,不如把这些属性打包成一个json字符串,在后端作为整体进行读写,数据库里面只需要一列(如Attrs),里面存储这个json对象即可。

    简而言之,我们需要一个相当于object的 protobuf 类型。

map 不行

    protobuf 里面的 map等价于 repeated MapFieldEntry,因此不支持 repeated map,即不能表达List<object>,另外map 里面值的类型不能混,因此map ≠ object。

Any 不行

    Any应该被视为C#里的强类型,只能pack/unpack另一个message,本身不能添加属性,也不能直接进行json的反序列化。我理解Any=IMessage,需要你提供一个具体的message类型,因此也不是弱类型。

Struct 正解

     官网上说,“Struct represents a structured data value, consisting of fields which map to dynamically typed values. In some languages, Struct might be supported by a native representation. For example, in scripting languages like JS a struct is represented as an object.

    这正是我要找的弱类型!

    在proto文件中,我们定义一个类型 Device,把除Id, Name之外的属性都放到 google.protobuf.Struct attrs 中,注意前面需要 import "google/protobuf/struct.proto" 才能使用Struct 。

syntax = "proto3";

import "google/protobuf/struct.proto";

option csharp_namespace = "StructExample";

message Device {
  int32 id = 1;
  string name = 2;
  google.protobuf.Struct attrs=3;
}

    之后会得到一个类,不过Attrs对象默认值为null,使用前要先赋值为 new Struct()。

public class Device
{
    public int Id{get;set;}
    
    public string Name{get;set;}

    public Struct Attrs{get;set;}
}

    然后我们可以创建一个对象,通过 Fields来添加属性,通过Value.For... 赋值。

var device1 = new Device()
{
    Id = 1,
    Name = "设备1",
    Attrs = new Struct()
};

device1.Attrs.Fields["生产厂家"] = Value.ForString("海尔");

     然后我们可以简单的用device1.ToString()获得对象的json字符串,也可以用这个字符串Device.Parser.ParseJson 得到对象,后端保存时Attrs字段就保存这个json 字符串。

var jsonString = device1.ToString();
//{ "id": 1, "name": "设备1", "attrs": { "生产厂家": "海尔" } }
var deviceCopy = Device.Parser.ParseJson(jsonString);

实现动态对象WebApi

    有了Attrs 这个Struct,我们什么都可以往里装,为了让前端完全不需要知道我们的实现机制,可以在rpc 服务实现时,把 Id, Name 两列也添加到Fields里,返回Attrs作为一个完整的json 对象。

    在proto文件中,可以定义一个GetDetail,区别于Get返回Device,GetDetail返回Struct,学问就在这了。

service DeviceRPC {
  rpc Get (RequestId) returns (Device){
	option(google.api.http)={
	  get:"/device/{id}"
	};
  }
  rpc GetDetail (RequestId) returns (google.protobuf.Struct){
	option(google.api.http)={
	  get:"/device/detail/{id}"
	};
  }
}

    后端实现时,可以这样构造GetDetail的返回值 

var result = new Struct();
result["id"] = Value.ForNumber(device1.Id);
result["name"] = Value.ForString(device1.Name);
if(device1.Attrs != null && device1.Attrs.Fields.Count > 0)
{
    foreach (var field in device1.Attrs.Fields)
    {
        result.Fields[field.Key] = field.Value;
    }
}

    /device/1 返回 { "id": 1, "name": "设备1", "attrs": { "生产厂家": "海尔" } }

    /device/detail/1 返回 { "id": 1, "name": "设备1",  "生产厂家": "海尔"  }

    我们也可以提供 repeated Struct 来返回多条结果,其中一个技巧是在返回的response_body中指定层级到items,这样返回的就是不带 items: 的干净json字符串了。

  rpc GetDeviceDetail(Request) returns(AttrsList){
	option(google.api.http)={
	  post:"/device/detail",
      response_body: "items"
	};
  }
  

message AttrsList{
  repeated google.protobuf.Struct items = 1;
}

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Protobuf中的结构定义被称为"message"类型,类似于C/C++中的struct。一个message可以包含不同类型的字段,比如字符串、整数、列表等。例如,可以用电话簿中的数据作为一个message的例子: ```protobuf message Person { string name; int32 id; string email; repeated int32 samples; }; ``` 在上面的例子中,Person是一个message类型,包含了name、id、email和samples这些字段。name和email是字符串类型,id是整数类型,samples是一个整数类型的列表。 当定义完proto文件后,需要使用protobuf提供的编译工具将proto文件编译成对应语言的源码。以C++为例,使用命令`protoc -I=./ --cpp_out=./ test.proto`会生成两个文件:`test.pb.h`和`test.pb.cc`,通常将`test.pb.cc`重命名为`test.pb.cpp`。这些文件包含了根据proto文件生成的对应的C++代码,可以用于序列化反序列化message。 Protobuf是由Google团队开发的用于高效存储和读取结构化数据的工具。相比于XML和JSON这类存储结构化数据的格式,使用Protobuf表示的数据更加高效,并且可以将数据压缩到更小的体积,大约是JSON格式的1/10,XML格式的1/20。因此,Protobuf在网络传输和存储数据方面具有优势。 在使用Protobuf进行序列化时,可以使用不同的函数进行反序列化。例如,可以使用`ParseFromIstream`从输入流解析,`ParseFromString`从字符串解析,以及`ParseFromArray`从字节流解析。这些函数可以将序列化后的数据转换为对应的message对象。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值