Skip to content

Commit

Permalink
feat(object): support FilterObjectEmptyField
Browse files Browse the repository at this point in the history
  • Loading branch information
ahuigo committed Aug 21, 2024
1 parent a436c91 commit 406caa0
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 29 deletions.
59 changes: 30 additions & 29 deletions object/obj.go → object/convert-obj-byte2string.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,39 @@ import (
"strings"
)

type PtrSeen map[uintptr]interface{}

func (ps PtrSeen) Add(rv reflect.Value) uintptr {
ptr := rv.Pointer()
if _, ok := ps[ptr]; ok {
e := fmt.Sprintf("encountered a cycle via %s", rv.Type())
panic(e)
}
ps[ptr] = struct{}{}
return ptr
}
type _StructByte2String map[uintptr]interface{}

func ConvertObjectByte2String(o interface{}) interface{} {
refV := reflect.ValueOf(o)
// var out interface{}
ps := PtrSeen{}
out := convertObjectByte2String(refV, ps)
sb := &_StructByte2String{}
out := sb.convertObjectByte2String(refV)
return out
}

func convertObjectByte2String(rv reflect.Value, ptrSeen PtrSeen) (out interface{}) {
func (sb *_StructByte2String) convertObjectByte2String(rv reflect.Value) (out interface{}) {
outV := reflect.ValueOf(&out).Elem()
t := rv.Type()

ptrSeen := *sb
switch t.Kind() {
case reflect.Slice:
ptr := ptrSeen.Add(rv)
ptr := sb.AddPtrSeen(rv)
defer delete(ptrSeen, ptr)
fallthrough
case reflect.Array:
// 882 go/1.18.1/libexec/src/encoding/json/encode.go
return convertSliceArray(rv, ptrSeen)
return sb.convertSliceArray(rv)
case reflect.Map:
m := convertMap(rv, ptrSeen)
m := sb.convertMap(rv)
outV.Set(reflect.ValueOf(m))
case reflect.Ptr:
ptr := ptrSeen.Add(rv)
ptr := sb.AddPtrSeen(rv)
defer delete(ptrSeen, ptr)
fallthrough
case reflect.Interface:
return convertPtrInterface(rv, ptrSeen)
return sb.convertPtrInterface(rv)
case reflect.Struct:
m := convertStruct(rv, ptrSeen)
m := sb.convertStruct(rv)
outV.Set(reflect.ValueOf(m))
case reflect.Chan:
default:
Expand All @@ -57,36 +47,47 @@ func convertObjectByte2String(rv reflect.Value, ptrSeen PtrSeen) (out interface{
return
}

func convertSliceArray(rv reflect.Value, ptrSeen PtrSeen) interface{} {
func (sb *_StructByte2String) AddPtrSeen(rv reflect.Value) uintptr {
ptrSeen := *sb
ptr := rv.Pointer()
if _, ok := ptrSeen[ptr]; ok {
e := fmt.Sprintf("encountered a cycle via %s", rv.Type())
panic(e)
}
ptrSeen[ptr] = struct{}{}
return ptr
}

func (sb *_StructByte2String) convertSliceArray(rv reflect.Value, ) interface{} {
if rv.Type().Elem().Kind() == reflect.Uint8 {
return string(rv.Bytes())
}
s := make([]interface{}, rv.Len())
for i := 0; i < rv.Len(); i++ {
s[i] = convertObjectByte2String(rv.Index(i), ptrSeen)
s[i] = sb.convertObjectByte2String(rv.Index(i))
}
return s
}

func convertMap(rv reflect.Value, ptrSeen PtrSeen) interface{} {
func (sb *_StructByte2String) convertMap(rv reflect.Value) interface{} {
m := make(map[string]interface{})
mi := rv.MapRange()
for mi.Next() {
k := mi.Key()
v := mi.Value()
m[k.String()] = convertObjectByte2String(v, ptrSeen)
m[k.String()] = sb.convertObjectByte2String(v)
}
return m
}

func convertPtrInterface(rv reflect.Value, ptrSeen PtrSeen) interface{} {
func (sb *_StructByte2String) convertPtrInterface(rv reflect.Value) interface{} {
if rv.IsNil() {
return nil
}
return convertObjectByte2String(rv.Elem(), ptrSeen)
return sb.convertObjectByte2String(rv.Elem())
}

func convertStruct(rv reflect.Value, ptrSeen PtrSeen) interface{} {
func (sb *_StructByte2String) convertStruct(rv reflect.Value) interface{} {
m := make(map[string]interface{})
t := rv.Type()
for i := 0; i < t.NumField(); i++ {
Expand All @@ -102,7 +103,7 @@ func convertStruct(rv reflect.Value, ptrSeen PtrSeen) interface{} {
}
}
fv := rv.Field(i)
v := convertObjectByte2String(fv, ptrSeen)
v := sb.convertObjectByte2String(fv)
if v == nil && isOmitEmpty {
continue
}
Expand Down
File renamed without changes.
132 changes: 132 additions & 0 deletions object/filter-obj-empty-field.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package object

import (
"fmt"
"reflect"
"strings"
)

type _FilterEmptyField map[uintptr]interface{}

func FilterObjectEmptyField(o interface{}) interface{} {
refV := reflect.ValueOf(o)
// var out interface{}
ins := &_FilterEmptyField{}
out := ins.filterObjectEmptyField(refV)
return out
}

func (ins *_FilterEmptyField) filterObjectEmptyField(rv reflect.Value) (out interface{}) {
outV := reflect.ValueOf(&out).Elem()
t := rv.Type()
ptrSeen := *ins
switch t.Kind() {
case reflect.Slice:
ptr := ins.AddPtrSeen(rv)
defer delete(ptrSeen, ptr)
fallthrough
case reflect.Array:
// 882 go/1.18.1/libexec/src/encoding/json/encode.go
// return ins.filterSliceArray(rv)
return rv.Interface()
case reflect.Map:
m := ins.filterMap(rv)
outV.Set(reflect.ValueOf(m))
case reflect.Struct:
m := ins.filterStruct(rv)
outV.Set(reflect.ValueOf(m))
case reflect.Ptr:
ptr := ins.AddPtrSeen(rv)
defer delete(ptrSeen, ptr)
fallthrough
case reflect.Interface:
return ins.filterPtrInterface(rv)
case reflect.Chan:
default:
return rv.Interface()
}
return
}

func (ins *_FilterEmptyField) AddPtrSeen(rv reflect.Value) uintptr {
ptrSeen := *ins
ptr := rv.Pointer()
if _, ok := ptrSeen[ptr]; ok {
e := fmt.Sprintf("encountered a cycle via %s", rv.Type())
panic(e)
}
ptrSeen[ptr] = struct{}{}
return ptr
}

// func (ins *_FilterEmptyField) filterSliceArray(rv reflect.Value) interface{} {
// if rv.Type().Elem().Kind() == reflect.Uint8 {
// return string(rv.Bytes())
// }
// s := make([]interface{}, rv.Len())
// for i := 0; i < rv.Len(); i++ {
// s[i] = ins.filterObjectEmptyField(rv.Index(i))
// }
// return s
// }

func (ins *_FilterEmptyField) filterMap(rv reflect.Value) interface{} {
m := make(map[string]interface{})
mr := rv.MapRange()
for mr.Next() {
v := mr.Value()
// key2 := mr.Key().String()
// _ = key2
if ins.isEmptyField(v) {
continue
}
key := mr.Key().String()
m[key] = v.Interface()
}
return m
}
func (ins *_FilterEmptyField) isEmptyField(v reflect.Value) bool {
// v.IsNil() is only for pointer, channel, func, interface, map, or slice
// !v.IsValid() : if v is from map[notExistKey], v is invalid
if v.IsZero() {
return true
}
switch v.Kind() {
// case Chan, Func, Interface, Map, Pointer, Slice, UnsafePointer:
case reflect.Slice:
return v.Len() == 0
default:
return false
}

}

func (ins *_FilterEmptyField) filterPtrInterface(rv reflect.Value) interface{} {
if rv.IsNil() {
return nil
}
return ins.filterObjectEmptyField(rv.Elem())
}

func (ins *_FilterEmptyField) filterStruct(rv reflect.Value) interface{} {
m := make(map[string]interface{})
t := rv.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
key := f.Name
// 1. is empty
fv := rv.Field(i)
if ins.isEmptyField(fv){
continue
}
// 2. set json tag name
if f.Tag != "" {
key = f.Tag.Get("json")
keys := strings.Split(key, ",")
key = keys[0]
}
v := fv.Interface()
m[key] = v
}
return m
}
45 changes: 45 additions & 0 deletions object/filter-obj-empty-field_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package object

import (
"encoding/json"
"fmt"
"testing"
)

func TestFilterEmptyFieldOfMap(t *testing.T) {
objMap := map[string][]byte{
"k1": []byte("v1"),
"k2": []uint8{},
"k3": nil,
}
out, _ := json.Marshal(objMap)
fmt.Println(string(out)) //output: {"k1":"djE=","k2":"djI="}

objString := FilterObjectEmptyField(objMap)
out, _ = json.Marshal(objString)
fmt.Println(string(out)) //output: {"k1":"v1","k2":"v2"}

expectedOut := `{"k1":"djE="}`
if string(out) != expectedOut {
t.Fatalf("expected out:%v, unexpected out: %v", expectedOut, string(out))
}
}

func TestFilterEmptyFieldOfStruct(t *testing.T) {
type HistoryEvent struct {
EventId *int64 `json:"eventId"`
TaskId *int64 `json:"taskId"`
}
i := int64(1)
obj := HistoryEvent{
EventId: &i,
}
if out, err := json.Marshal(FilterObjectEmptyField(obj)); err != nil {
t.Fatal(err)
} else {
expectedOut := `{"eventId":1}`
if string(out) != expectedOut {
t.Fatalf("expected out:%v, unexpected out: %v", expectedOut, string(out))
}
}
}

0 comments on commit 406caa0

Please sign in to comment.