Skip to content

Commit

Permalink
支持protobuf和json的struct tag中的别名
Browse files Browse the repository at this point in the history
  • Loading branch information
fish-tennis committed Oct 28, 2024
1 parent 4d6305d commit 820fc59
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 7 deletions.
77 changes: 75 additions & 2 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,23 @@ func ConvertCsvLineToValue(valueType reflect.Type, row []string, columnNames []s
if valueType.Kind() == reflect.Struct {
newObject = newObject.Elem() // *pb.ItemCfg -> pb.ItemCfg
}
// protobuf alias name map
var aliasNames map[string]string
for columnIndex := 0; columnIndex < len(columnNames); columnIndex++ {
columnName := columnNames[columnIndex]
fieldString := row[columnIndex]
fieldVal := newObjectElem.FieldByName(columnName)
if !fieldVal.IsValid() {
if aliasNames == nil {
aliasNames = getAliasNameMap(valueElemType, option)
}
// xxx.proto里定义的字段名可能是cfg_id
// 生成的xxx.pb里面的字段名会变成CfgId
// 如果csv里面的列名使用cfg_id也要能解析
if realFieldName, ok := aliasNames[columnName]; ok {
fieldVal = newObjectElem.FieldByName(realFieldName)
}
}
if fieldVal.Kind() == reflect.Ptr { // 指针类型的字段,如 Name *string
fieldObj := reflect.New(fieldVal.Type().Elem()) // 如new(string)
fieldVal.Set(fieldObj) // 如 obj.Name = new(string)
Expand Down Expand Up @@ -281,12 +294,12 @@ func ParseNestStringSlice(cellString string, option *CsvOption, nestFieldNames .
break
}
// 从beginPos后面的位置查找}
after := remain[beginPos + len(keyword):] // 如CfgId_1#Num_1;CfgId_2#Num_1}
after := remain[beginPos+len(keyword):] // 如CfgId_1#Num_1;CfgId_2#Num_1}
endPos := strings.Index(after, "}")
if endPos < 0 {
break
}
endPos += beginPos+len(keyword)
endPos += beginPos + len(keyword)
nestFieldValue := remain[beginPos+len(keyword) : endPos] // 如CfgId_1#Num_1;CfgId_2#Num_1
idCounter++
replaceKeys[idCounter] = &StringPair{
Expand Down Expand Up @@ -400,3 +413,63 @@ func ConvertStringToRealType(typ reflect.Type, s string) any {
}
return nil
}

func getAliasNameMap(elemType reflect.Type, option *CsvOption) map[string]string {
aliasNames := make(map[string]string)
if option.DisableProtobufAliasName && option.DisableJsonAliasName {
return aliasNames
}
for i := 0; i < elemType.NumField(); i++ {
fieldTyp := elemType.Field(i)
if !fieldTyp.IsExported() {
continue
}
if !option.DisableProtobufAliasName {
name := getProtobufNameFromStructTag(fieldTyp.Tag)
if name == "" {
continue
}
aliasNames[name] = fieldTyp.Name
}
if !option.DisableJsonAliasName {
name := getJsonNameFromStructTag(fieldTyp.Tag)
if name == "" {
continue
}
aliasNames[name] = fieldTyp.Name
}
}
return aliasNames
}

func getProtobufNameFromStructTag(tag reflect.StructTag) string {
tagString, ok := tag.Lookup("protobuf")
if !ok {
return ""
}
// proto2 Num *int32 `protobuf:"varint,1,opt,name=num"`
// proto3 Num int32 `protobuf:"varint,1,opt,name=num,proto3"`
nameIdx := strings.Index(tagString, "name=")
if nameIdx < 0 {
return ""
}
name := tagString[nameIdx+5:]
endIdx := strings.Index(name, ",")
if endIdx > 0 {
name = name[:endIdx]
}
return name
}

func getJsonNameFromStructTag(tag reflect.StructTag) string {
name, ok := tag.Lookup("json")
if !ok {
return ""
}
// Num int32 `json:"num,omitempty"`
endIdx := strings.Index(name, ",")
if endIdx > 0 {
name = name[:endIdx]
}
return name
}
31 changes: 28 additions & 3 deletions csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ type CsvOption struct {
// 字段名数据行索引(>=0)
ColumnNameRowIndex int

// key-value格式的csv数据给对象赋值,数据行索引(>=0)
ObjectDataBeginRowIndex int

// 是否禁用protobuf的字段别名(struct tag里的name),默认不禁用
// proto2示例 Num *int32 `protobuf:"varint,1,opt,name=num"`
// proto3示例 Num int32 `protobuf:"varint,1,opt,name=num,proto3"`
DisableProtobufAliasName bool

// 是否禁用json的字段别名(struct tag里的name),默认不禁用
// 示例 Num *int32 `json:"num,omitempty"`
DisableJsonAliasName bool

// 数组分隔符
// 如数组分隔符为;时,则1;2;3可以表示[1,2,3]的数组
SliceSeparator string
Expand Down Expand Up @@ -225,21 +237,34 @@ func ReadCsvFromDataObject[V any](rows [][]string, v V, option *CsvOption) error
if len(rows[0]) < 2 {
return errors.New("column count must >= 2")
}
if option.DataBeginRowIndex < 1 {
return errors.New("DataBeginRowIndex must >=1")
if option.ObjectDataBeginRowIndex < 1 {
return errors.New("ObjectDataBeginRowIndex must >=1")
}
typ := reflect.TypeOf(v) // type of v, 如*pb.ItemCfg or pb.ItemCfg
val := reflect.ValueOf(v)
if typ.Kind() != reflect.Ptr {
return errors.New("v must be Ptr")
}
valElem := val.Elem() // *pb.ItemCfg -> pb.ItemCfg
for rowIndex := option.DataBeginRowIndex; rowIndex < len(rows); rowIndex++ {
// protobuf alias name map
var aliasNames map[string]string
for rowIndex := option.ObjectDataBeginRowIndex; rowIndex < len(rows); rowIndex++ {
row := rows[rowIndex]
// key-value的固定格式,列名不用
columnName := row[0]
fieldString := row[1]
fieldVal := valElem.FieldByName(columnName)
if !fieldVal.IsValid() {
if aliasNames == nil {
aliasNames = getAliasNameMap(valElem.Type(), option)
}
// xxx.proto里定义的字段名可能是cfg_id
// 生成的xxx.pb里面的字段名会变成CfgId
// 如果csv里面的列名使用cfg_id也要能解析
if realFieldName, ok := aliasNames[columnName]; ok {
fieldVal = valElem.FieldByName(realFieldName)
}
}
if fieldVal.Kind() == reflect.Ptr { // 指针类型的字段,如 Name *string
fieldObj := reflect.New(fieldVal.Type().Elem()) // 如new(string)
fieldVal.Set(fieldObj) // 如 obj.Name = new(string)
Expand Down
25 changes: 23 additions & 2 deletions csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ type ItemCfg struct {

// 物品Id和数量
type ItemNum struct {
CfgId int32 `json:"CfgId,omitempty"` // 物品配置id
Num int32 `json:"Num,omitempty"` // 物品数量
CfgId int32 `json:"CfgId,omitempty" protobuf:"varint,1,opt,name=cfg_id"` // 物品配置id
Num int32 `json:"Num,omitempty" protobuf:"varint,1,opt,name=num,proto3"` // 物品数量
}

// 模拟一个protobuf定义的枚举
Expand Down Expand Up @@ -337,3 +337,24 @@ func TestNestStruct(t *testing.T) {
}
}
}

func TestAliasName(t *testing.T) {
type s struct {
CfgId int32 `json:"cfgid,omitempty" protobuf:"varint,1,opt,name=cfg_id"`
StrName string `json:"strname,omitempty" protobuf:"varint,1,opt,name=str_name,proto3"`
}
rows := [][]string{
{"cfgid", "str_name"},
{"1", "abc"},
{"2", "def"},
{"3", "ghi"},
}
m := make(map[int32]*s)
err := ReadCsvFromDataMap(rows, m, nil)
if err != nil {
t.Fatal(err)
}
for _, item := range m {
t.Logf("%v", item)
}
}

0 comments on commit 820fc59

Please sign in to comment.