Skip to content

Commit

Permalink
修复对nil interface{}解析时panic的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
liu-cn committed Jan 14, 2023
1 parent d459a50 commit 17f44b6
Show file tree
Hide file tree
Showing 11 changed files with 393 additions and 78 deletions.
233 changes: 233 additions & 0 deletions filter/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package filter

import (
"reflect"
)

var fieldCacheVar fieldCache

func init() {
fieldCacheVar.maps = make(map[string]*field)
}

type fieldCache struct {
maps map[string]*field
}
type field struct {
index int //该字段所处结构体的索引位置
tag tag
selectFields []*field
}

func newField(t tag, idx int) field {
return field{
tag: t,
index: idx,
}
}

func getCacheKey(pkgInfo string, scene string, isSelect bool) string {
mode := ".s"
if !isSelect {
mode = ".o"
}
return pkgInfo + "." + scene + mode
}
func (t *fieldNodeTree) parseAny2(key, scene string, valueOf reflect.Value, isSelect bool) {
typeOf := valueOf.Type()
TakePointerValue: //取指针的值
switch typeOf.Kind() {
case reflect.Ptr: //如果是指针类型则取值重新判断类型
valueOf = valueOf.Elem()
typeOf = typeOf.Elem()
goto TakePointerValue
case reflect.Interface:
if !valueOf.IsNil() {
valueOf = reflect.ValueOf(valueOf.Interface())
typeOf = valueOf.Type()
goto TakePointerValue
} else {
parserNilInterface(t, key)
}
case reflect.Struct:
parserStructCache(typeOf, valueOf, t, scene, key, isSelect)
case reflect.Bool,
reflect.String,
reflect.Float64, reflect.Float32,
reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int,
reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
parserBaseType(valueOf, t, key)
case reflect.Map:
parserMap(valueOf, t, scene, isSelect)
case reflect.Slice, reflect.Array:
parserSliceOrArray(typeOf, valueOf, t, scene, key, isSelect)
}

}

func parserStructCache(typeOf reflect.Type, valueOf reflect.Value, t *fieldNodeTree, scene string, key string, isSelect bool) {
if valueOf.CanConvert(timeTypes) { //是time.Time类型或者底层是time.Time类型
t.Key = key
t.Val = valueOf.Interface()
return
}
if typeOf.NumField() == 0 { //如果是一个struct{}{}类型的字段或者是一个空的自定义结构体编码为{}
t.Key = key
t.Val = struct{}{}
return
}
pkgInfo := typeOf.PkgPath() + "." + typeOf.Name()

cacheKey := getCacheKey(pkgInfo, scene, isSelect)
cacheField, ok := fieldCacheVar.maps[cacheKey]
if !ok {

var parentField *field
if t.isRoot {
//fieldCacheVar.maps[cacheKey] = &field{}
parentField = new(field)
fieldCacheVar.maps[cacheKey] = parentField
} else {
parentField = t.fieldCache
}

for i := 0; i < typeOf.NumField(); i++ {
var tagInfo tagInfo
tagInfo = getSelectTag(scene, pkgInfo, i, typeOf)
if !isSelect {
tagInfo = getOmitTag(scene, pkgInfo, i, typeOf)
}

if tagInfo.omit {
continue
}
tag := tagInfo.tag
if tag.IsOmitField || !tag.IsSelect {
continue
}
isAnonymous := typeOf.Field(i).Anonymous && tag.IsAnonymous ////什么时候才算真正的匿名字段? Book中Article才算匿名结构体
tag.IsAnonymous = isAnonymous
fieldCache := newField(tag, i)
fieldEl, ok1 := fieldCacheVar.maps[cacheKey]
if ok1 {
fieldEl.selectFields = append(fieldEl.selectFields, &fieldCache)
//fieldCacheVar.maps[cacheKey].selectFields = append(fieldCacheVar.maps[cacheKey].selectFields, &fieldCache)
}

tree := &fieldNodeTree{
Key: tag.UseFieldName,
ParentNode: t,
IsAnonymous: isAnonymous,
Tag: tag,
fieldCache: &fieldCache,
}
value := valueOf.Field(i)
if tag.Function != "" {
function := valueOf.MethodByName(tag.Function)
if !function.IsValid() {
if valueOf.CanAddr() {
function = valueOf.Addr().MethodByName(tag.Function)
}
}
if function.IsValid() {
values := function.Call([]reflect.Value{})
if len(values) != 0 {
value = values[0]
}
}
}
if value.Kind() == reflect.Ptr {
TakeFieldValue:
if value.Kind() == reflect.Ptr {
if value.IsNil() {
if tag.Omitempty {
continue
}
tree.IsNil = true
t.AddChild(tree)
continue
} else {
value = value.Elem()
goto TakeFieldValue
}
}
} else {
if tag.Omitempty {
if value.IsZero() { //为零值忽略
continue
}
}
}

tree.parseAny2(tag.UseFieldName, scene, value, isSelect)

if t.IsAnonymous {
t.AnonymousAddChild(tree)
} else {
t.AddChild(tree)
}
}
} else { //说明缓存取到
for i := 0; i < len(cacheField.selectFields); i++ {
fieldInfo := cacheField.selectFields[i]
tag := fieldInfo.tag
tree := &fieldNodeTree{
Key: tag.UseFieldName,
ParentNode: t,
IsAnonymous: tag.IsAnonymous,
Tag: tag,
fieldCache: fieldInfo,
}
value := valueOf.Field(fieldInfo.index)
if tag.Function != "" {
function := valueOf.MethodByName(tag.Function)
if !function.IsValid() {
if valueOf.CanAddr() {
function = valueOf.Addr().MethodByName(tag.Function)
}
}
if function.IsValid() {
values := function.Call([]reflect.Value{})
if len(values) != 0 {
value = values[0]
}
}
}
if value.Kind() == reflect.Ptr {
TakeFieldValue1:
if value.Kind() == reflect.Ptr {
if value.IsNil() {
if tag.Omitempty {
continue
}
tree.IsNil = true
t.AddChild(tree)
continue
} else {
value = value.Elem()
goto TakeFieldValue1
}
}
} else {
if tag.Omitempty {
if value.IsZero() { //为零值忽略
continue
}
}
}

tree.parseAny2(tag.UseFieldName, scene, value, isSelect)

if t.IsAnonymous {
t.AnonymousAddChild(tree)
} else {
t.AddChild(tree)
}
}
}

if t.Children == nil && !t.IsAnonymous {
t.Val = struct{}{} //这样表示返回{}
}

}
23 changes: 23 additions & 0 deletions filter/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func Select(selectScene string, el interface{}) interface{} {
func jsonFilter(selectScene string, el interface{}, isSelect bool) Filter {
tree := &fieldNodeTree{
Key: "",
isRoot: true,
ParentNode: nil,
}
tree.parseAny("", selectScene, reflect.ValueOf(el), isSelect)
Expand Down Expand Up @@ -81,3 +82,25 @@ func (f Filter) String() string {
}
return json
}

//// SelectCache 直接返回过滤后的数据结构,它可以被json.Marshal解析,直接打印会以过滤后的json字符串展示
//func SelectCache(selectScene string, el interface{}) interface{} {
// return jsonFilterCache(selectScene, el, true)
//}
//
//func jsonFilterCache(selectScene string, el interface{}, isSelect bool) Filter {
// tree := &fieldNodeTree{
// Key: "",
// isRoot: true,
// ParentNode: nil,
// }
// tree.parseAny2("", selectScene, reflect.ValueOf(el), isSelect)
// return Filter{
// node: tree,
// }
//}
//
//// OmitCache 直接返回过滤后的数据结构,它可以被json.Marshal解析,直接打印会以过滤后的json字符串展示
//func OmitCache(selectScene string, el interface{}) interface{} {
// return jsonFilterCache(selectScene, el, false)
//}
2 changes: 1 addition & 1 deletion filter/json_marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestJsonMarshal(t *testing.T) {
Lang: "Go",
}

f := SelectMarshal("lang", user)
f := Select("lang", user)
marshal, err := json.Marshal(f)
if err != nil {
panic(err)
Expand Down
3 changes: 3 additions & 0 deletions filter/node_decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import (

type fieldNodeTree struct {
isSelect bool //是否是select 方法
isRoot bool //是过滤的入口结构体
Key string //字段名
Val interface{} //字段值,基础数据类型,int string,bool 等类型直接存在这里面,如果是struct,切片数组map 类型则字段所有k v会存在ChildNodes里
IsSlice bool //是否是切片,或者数组,
IsAnonymous bool //是否是匿名结构体,内嵌结构体,需要把所有字段展开
IsNil bool //该字段值是否为nil
ParentNode *fieldNodeTree //父节点指针,根节点为nil,
Children []*fieldNodeTree //如果是struct则保存所有字段名和值的指针,如果是切片就保存切片的所有值
Tag tag //标签
fieldCache *field //缓存
}

func (t *fieldNodeTree) GetValue() (val interface{}, ok bool) {
Expand Down
27 changes: 24 additions & 3 deletions filter/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ TakePointerValue: //取指针的值
typeOf = typeOf.Elem()
goto TakePointerValue
case reflect.Interface:
valueOf = reflect.ValueOf(valueOf.Interface())
typeOf = valueOf.Type()
goto TakePointerValue
if !valueOf.IsNil() {
valueOf = reflect.ValueOf(valueOf.Interface())
typeOf = valueOf.Type()
goto TakePointerValue
} else {
parserNilInterface(t, key)
}

case reflect.Struct:
parserStruct(typeOf, valueOf, t, scene, key, isSelect)
case reflect.Bool,
Expand All @@ -38,6 +43,22 @@ TakePointerValue: //取指针的值

}

func parserNilInterface(t *fieldNodeTree, key string) {
if t.IsAnonymous {
tree := &fieldNodeTree{
Key: t.Key,
ParentNode: t,
Val: t.Val,
IsNil: true,
}
t.AnonymousAddChild(tree)
} else {
t.Val = nil
t.Key = key
t.IsNil = true
}
}

func getFieldOmitTag(field reflect.StructField, scene string) tagInfo {
tagInfoEl := tagInfo{}
//没开缓存就获取tag
Expand Down
25 changes: 11 additions & 14 deletions filter/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,20 @@ const (
)

type tag struct {
//执行的场景
SelectScene string
//该字段是否需要被忽略?
IsOmitField bool
//是选中的情况,标识该字段是否需要被解析
IsSelect bool
//字段名称
UseFieldName string
//IsAnonymous 标识该字段是否是匿名字段
IsAnonymous bool
////为空忽略
Omitempty bool
//自定义处理函数
Function string
SelectScene string //执行的场景
IsOmitField bool //该字段是否需要被忽略?
IsSelect bool //是选中的情况,标识该字段是否需要被解析
FieldName string //字段名
UseFieldName string //最终使用的字段名,没标签就用结构体字段名,tag里有标签就用标签名字
IsAnonymous bool //IsAnonymous 标识该字段是否是匿名字段
Omitempty bool //为空忽略
Function string //自定义处理函数
}

func newSelectTag(tagStr, selectScene, fieldName string) tag {

tagEl := tag{
FieldName: fieldName,
SelectScene: selectScene,
IsOmitField: true,
}
Expand Down Expand Up @@ -69,6 +64,7 @@ func newSelectTag(tagStr, selectScene, fieldName string) tag {

func newOmitTag(tagStr, omitScene, fieldName string) tag {
tagEl := tag{
FieldName: fieldName,
SelectScene: omitScene,
IsOmitField: false,
IsSelect: true,
Expand Down Expand Up @@ -116,6 +112,7 @@ func newOmitTag(tagStr, omitScene, fieldName string) tag {

func newOmitNotTag(omitScene, fieldName string) tag {
return tag{
FieldName: fieldName,
IsSelect: true,
UseFieldName: fieldName,
SelectScene: omitScene,
Expand Down
Loading

0 comments on commit 17f44b6

Please sign in to comment.