3

自古以来,反射也是兵家必争之地 - 博客猿马甲哥

 1 year ago
source link: https://www.cnblogs.com/JulianHuang/p/17437421.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

大概是下表这样:

序号 字段名 描述 是否必填
0 logVersion 日志版本
1 productName 产品
2 serviceName 服务
...
...
...
25 extend3 扩展字段3
26 extend4 扩展字段3
27 extend5 扩展字段3
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

控制点1:必填字段少,若可选字段无值,该字段序列化为“null”;

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

这不就是自定义序列化 且设置序列化默认值?

真要我挨个字段填充,我眼睛都要对花, 而且很容易漏掉字段。

// 伪代码如下:
  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. 使用struct来定义结构,便于对必填字段赋值 (这个行为肉眼友好)
  2. 将struct的[字段:字段值]转换为排好序的map键值对
  3. 对排好序的map键值对无脑序列化

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

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

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

  1. golang付map做for循环,键值对的出现是随机的。
  2. 函数传参注意传指针值,而不要传结构体值。

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

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

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

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循环,键值对的出现是随机的; 对keys排序,根据排序的keys再取map键值对要随机应变。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK