自古以来,反射也是兵家必争之地

文章讲述了在Golang中如何处理一个战术性需求,即将包含必填和可选字段的结构体序列化为特定格式的字符串。作者提到使用struct定义结构并借助反射将结构体转换为按字段顺序排列的map,然后进行有序的序列化,以解决字段填充繁琐且易出错的问题。文章重点讨论了反射的运用以及在处理map时遇到的键值对顺序问题,并给出了解决方案。
摘要由CSDN通过智能技术生成

28e498647efd269c979f2efbb5fc6cea.gif

        成文耗时1小时,阅读5min,有用指数5颗星。

     这几天收到一个战术性需求,将一大坨字段序列化为特定格式的字符串。

大概是下表:

序号字段名描述是否必填
0logVersion日志版本
1productName产品
2serviceName服务
...


...


...


25extend3扩展字段3
26extend4扩展字段3
27extend5扩展字段3
控制点1 : 必填字段少,若可选字段无值,则该字段序列化为字符串"null"。

控制点2: 序列化时字段有序,字段值之间用空格区分。

checklist-client com.CommonApiController uploadImage 2017-12-27 10:35:08 378 1.0 null null 192.168.35.12 EBJ4945 null null ylKLPAvAsoaWRnqGZhZ6xqZ6hkYxSrVKsQDOSOpwXgAAAA== 0 91 null null 0 202226d4-255f-891c-b627-9efc28ef366b 0 010 -1 null null null null null null

这不就是序列化器吗?要我挨个字段填充,我眼睛都要对花, 而且很容易漏掉字段。

// 伪代码如下:
  b := bytes.Buffer{}
  b.WriteString("P1")
  b.WriteString(" ")
  b.WriteString("null")
  b.WriteString(" ")
  b.WriteString("null")
  b.WriteString(" ")
  b.WriteString("A")
  ...
  b.WriteString(" ")
  b.WriteString("null")
  log.Info(b.String())

根据"必填字段极少,可选字段默认设为null字符串"的背景,我开始自定义序列化器:

  1. 1. 使用struct来定义结构,便于对必填字段赋值 (这个行为肉眼友好)

  2. 2. 将struct的[字段:字段值]转换为排好序的map键值对(并修正默认值)

  3. 3. 对排好序的map键值对无脑序列化

2c03e6123cf91dfa445af308ce2bee93.png

将结构体转换为 map, 这个行为涉及元类型的变动,联想到反射。

自古以来,反射也是兵家必争之地, 于是首次操刀golang的反射特性。

思路和伪代码很明确,实操时还是有2点障碍:

  1. 1. golang付map做for循环,键值对的出现是随机的。

  2. 2. 函数传参注意传指针值,而不要传结构体值。

关于第一个问题,利用网上的[提取key放在slice里面,再根据key的排序取map值]的思路是想当然了。
我们的key是字符串,sort.Strings()之后keys的排序依旧不是自己的预期(预期是按照struct字段出现的先后顺序)。

所以对map做for循环时,能拿到与struct字段出现顺序一致的键值对就是关键。

取巧:

我们利用反射struct时的字段顺序,定义了一个按照struct字段出现顺序为键的map[int]string
这样sort.Ints(keys) 排序之后,for keys能拿到我们想要的键值对顺序。

func constructFixedMap(body interface{}) map[int]string {
   
   typ := reflect.TypeOf(body)   //TypeOf返回目标数据类型
   val := reflect.ValueOf(body)  //ValueOf返回目标数据的的值
   if typ.Kind() != reflect.Pointer {
       fmt.Println("expect pointer")
       return nil
   }

   typ = typ.Elem() // 返回指针所指向的原值
   val = val.Elem()
   mp := make(map[int]string, 20)
   for i := 0; i < typ.NumField(); i++ { 
       if typ.Field(i).Type.Kind().String() == "string" {
           if val.Field(i).String() == "" {    // 可选字段,在反射时被修改
               mp[i] = "null"
           } else {
               mp[i] = val.Field(i).String()     // 必填字段,保持不变
           }
       } else {
           if val.Field(i).CanInt() {
               mp[i] = strconv.FormatInt(val.Field(i).Int(), 10)
           } else {
               mp[i] = "null"
           }

       }
   }
   return mp
}
记忆点回顾
  • • golang 反射的运用

  • • 对map做for循环,键值对的出现是随机的
        tip:对keys排序,根据排序的keys再取map键值对可随机应变。

8541cbb1f4976c88baa73501afdc1781.gif

年终总结:2021技术文大盘点  |  打包过去,面向未来

项目总结:麻雀虽小,五脏俱全

理念总结:实话实说:只会.NET,会让我们一直处于鄙视链、食物链的下游

云原生系列: 什么是云原生?

点“f9721571888de25d0f74e1938f5203d4.gif戳“在看037fd18631aab23cada1de4fc012faa9.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有态度的马甲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值