From cbb6b55b25c366cc04626dfe3f19bfbce066cec5 Mon Sep 17 00:00:00 2001 From: Nicolas Mahe Date: Wed, 11 Sep 2019 12:15:14 +0700 Subject: [PATCH] Seralize struct field interface without hash tag - PR #1318 - original commit fe442190cc7ef81cb15efe34c168aec058ab0bcd --- hash/structhash/structhash.go | 21 ++++++++++++++---- hash/structhash/structhash_test.go | 35 ++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/hash/structhash/structhash.go b/hash/structhash/structhash.go index 3b129985c..0d50188dd 100644 --- a/hash/structhash/structhash.go +++ b/hash/structhash/structhash.go @@ -91,15 +91,22 @@ func write(buf *bytes.Buffer, v reflect.Value) *bytes.Buffer { buf.WriteString("nil") return buf } - if v.CanInterface() { - write(buf, v.Elem()) - } + write(buf, v.Elem()) case reflect.Struct: vt := v.Type() items := make([]string, 0) for i := 0; i < v.NumField(); i++ { sf := vt.Field(i) to := parseTag(sf) + + // NOTE: structhash will allow to process all interface types. + // gogo/protobuf is not able to set tags for directly oneof interface. + // see: https://github.com/gogo/protobuf/issues/623 + if to.name == "" && reflect.Zero(sf.Type).Kind() == reflect.Interface { + to.skip = false + to.name = sf.Name + } + if to.skip || to.omitempty && isEmptyValue(v.Field(i)) { continue } @@ -186,10 +193,16 @@ type tagOptions struct { // comma-separated options. func parseTag(f reflect.StructField) tagOptions { tag := f.Tag.Get("hash") - if tag == "" || tag == "-" { + if tag == "" { + // NOTE: skip - interface with no tags can still be serialzied return tagOptions{skip: true} } + if tag == "-" { + // force skip - set name for "-" + return tagOptions{name: "-", skip: true} + } + var to tagOptions options := strings.Split(tag, ",") for _, option := range options { diff --git a/hash/structhash/structhash_test.go b/hash/structhash/structhash_test.go index 9547cbe0d..5ac573f46 100644 --- a/hash/structhash/structhash_test.go +++ b/hash/structhash/structhash_test.go @@ -69,6 +69,37 @@ func TestDump(t *testing.T) { }{nil}, "{a:nil}", }, + { + struct { + a interface{} `hash:"name:a"` + }{ + struct { + b map[string]int `hash:"name:b"` + }{ + map[string]int{ + "c": 1, + }, + }, + }, + "{a:{b:(\"c\":1)}}", + }, + // NOTE: structhash will allow to process all interface types. + // gogo/protobuf is not able to set tags for directly oneof interface. + // see: https://github.com/gogo/protobuf/issues/623 + { + struct { + a interface{} + }{ + struct { + b map[string]int `hash:"name:b"` + }{ + map[string]int{ + "c": 1, + }, + }, + }, + "{a:{b:(\"c\":1)}}", + }, { struct { A interface{} `hash:"name:A"` @@ -112,7 +143,7 @@ func TestDump(t *testing.T) { for _, tt := range tests { s := Dump(tt.v) - assert.Equalf(t, []byte(tt.s), s, "type %s: %v", reflect.TypeOf(tt.v), tt.v) + assert.Equalf(t, tt.s, string(s), "type %s: %v", reflect.TypeOf(tt.v), tt.v) } } @@ -185,7 +216,7 @@ func TestTag(t *testing.T) { } for _, tt := range tests { - assert.Equalf(t, []byte(tt.s), serialize(tt.v), "type %s: %v", reflect.TypeOf(tt.v), tt.v) + assert.Equalf(t, tt.s, string(serialize(tt.v)), "type %s: %v", reflect.TypeOf(tt.v), tt.v) } }