From ffaf2ee7a4ef52472c5b7eea271913f92f153b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=B5=E4=BB=AA?= Date: Wed, 16 Apr 2025 21:15:02 +0800 Subject: [PATCH 1/4] feat:(utils) support changing tag --- go.mod | 1 + go.sum | 2 + utils/tags.go | 132 +++++++++++++++++++++++++++++++++++++++++++++ utils/tags_test.go | 88 ++++++++++++++++++++++++++++++ 4 files changed, 223 insertions(+) create mode 100644 utils/tags.go create mode 100644 utils/tags_test.go diff --git a/go.mod b/go.mod index 70a0b1b96..f0191bd49 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/bytedance/sonic/loader v0.2.4 github.com/cloudwego/base64x v0.1.5 github.com/davecgh/go-spew v1.1.1 + github.com/fatih/structtag v1.2.0 github.com/klauspost/cpuid/v2 v2.0.9 github.com/stretchr/testify v1.8.1 github.com/twitchyliquid64/golang-asm v0.15.1 diff --git a/go.sum b/go.sum index 7839159a9..ed7abace2 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= diff --git a/utils/tags.go b/utils/tags.go new file mode 100644 index 000000000..0cd5c2749 --- /dev/null +++ b/utils/tags.go @@ -0,0 +1,132 @@ +/** + * Copyright 2025 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package utils + +import ( + "reflect" + + "github.com/fatih/structtag" +) + +type TagChanger struct { + types map[reflect.Type]reflect.Type // old -> new + replacer func(field reflect.StructField, tag reflect.StructTag) reflect.StructTag +} + +func NewTagChanger(replacer func(field reflect.StructField, tag reflect.StructTag) reflect.StructTag) *TagChanger { + return &TagChanger{ + types: make(map[reflect.Type]reflect.Type), + replacer: replacer, + } +} + +func (t *TagChanger) ReplaceTag(old reflect.Type) reflect.Type { + if t.types == nil { + t.types = make(map[reflect.Type]reflect.Type) + } + + // 如果已经处理过该类型,直接返回 + if n, ok := t.types[old]; ok { + return n + } + + // 处理结构体类型 + if old.Kind() == reflect.Struct { + // 创建一个新的结构体类型 + newType := reflect.StructOf(t.createFieldsWithReplacedTags(old)) + t.types[old] = newType + return newType + } + + // 处理map类型 + if old.Kind() == reflect.Map { + // 递归处理map的value类型 + nn := t.ReplaceTag(old.Elem()) + n := reflect.MapOf(old.Key(), nn) + t.types[old] = n + return n + } + + // 处理slice/array类型 + if old.Kind() == reflect.Slice || old.Kind() == reflect.Array { + // 递归处理元素类型 + nn := t.ReplaceTag(old.Elem()) + n := reflect.SliceOf(nn) + t.types[old] = n + return n + } + + // 处理指针类型 + if old.Kind() == reflect.Ptr { + // 递归处理指针指向的类型 + nn := t.ReplaceTag(old.Elem()) + n := reflect.PtrTo(nn) + t.types[old] = n + return n + } + + // 其他类型直接返回 + return old +} + +func (t *TagChanger) createFieldsWithReplacedTags(old reflect.Type) []reflect.StructField { + numField := old.NumField() + fields := make([]reflect.StructField, numField) + + for i := 0; i < numField; i++ { + oldField := old.Field(i) + tag := oldField.Tag + + // 递归处理处理字段类型 + fieldType := t.ReplaceTag(oldField.Type) + + // 使用自定义的replace函数替换tag + newTag := t.replacer(oldField, tag) + + fields[i] = reflect.StructField{ + Name: oldField.Name, + Type: fieldType, + Tag: newTag, + } + } + + return fields +} + + +func ReplacerAddOmitemptyfunc(field reflect.StructField, tag reflect.StructTag) reflect.StructTag { + tags, err := structtag.Parse(string(tag)) + if err != nil { + return tag + } + + // 查找 json tag + jsonTag, err := tags.Get("json") + if err != nil { + return reflect.StructTag(`json:",omitempty"`) + } + + // 修改 json tag 的值 + if !jsonTag.HasOption("omitempty") { + jsonTag.Options = append(jsonTag.Options, "omitempty") + } + + // 重新构建 tag + tags.Set(jsonTag) + newTag := tags.String() + return reflect.StructTag(newTag) +} \ No newline at end of file diff --git a/utils/tags_test.go b/utils/tags_test.go new file mode 100644 index 000000000..a3f9d742a --- /dev/null +++ b/utils/tags_test.go @@ -0,0 +1,88 @@ +/** + * Copyright 2025 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package utils + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + "unsafe" +) + +func TestTagChanger_ReplaceTag(t *testing.T) { + + type caseStruct struct { + A *int + B map[int]struct{ + C *int `json:"c"` + } + D []struct{ + E *int `json:"e"` + } + F *struct{ + G *int `json:"g"` + } + H struct{ + I *int `json:"i"` + } + } + + + type fields struct { + types map[reflect.Type]reflect.Type + replacer func(field reflect.StructField, tag reflect.StructTag) reflect.StructTag + } + type args struct { + old interface{} + } + tests := []struct { + name string + args args + want string + }{ + { + name: "test1", + args: args{ + old: caseStruct{}, + }, + want: `{"H":{}}`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := NewTagChanger(ReplacerAddOmitemptyfunc) + oldObj := caseStruct{} + oldJs, err := json.Marshal(oldObj) + if err!= nil { + t.Errorf("TagChanger.ReplaceTag() error = %v", err) + return + } + fmt.Println(string(oldJs)) + got := tr.ReplaceTag(reflect.TypeOf(tt.args.old)) + gotObj := reflect.NewAt(got, unsafe.Pointer(&oldObj)).Interface() + js, err := json.Marshal(gotObj) + println(string(js)) + if err!= nil { + t.Errorf("TagChanger.ReplaceTag() error = %v", err) + return + } else if string(js)!= tt.want { + t.Errorf("TagChanger.ReplaceTag() = %v, want %v", string(js), tt.want) + } + }) + } +} From 50125c55bb08d97ad40cfac947efadd0af6b2f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=B5=E4=BB=AA?= Date: Wed, 16 Apr 2025 21:25:01 +0800 Subject: [PATCH 2/4] add MarshalWithTagChanger --- utils/tags.go | 40 ++++++++++++++++++++++++++++++++++------ utils/tags_test.go | 2 +- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/utils/tags.go b/utils/tags.go index 0cd5c2749..ae45f1d2b 100644 --- a/utils/tags.go +++ b/utils/tags.go @@ -17,8 +17,11 @@ package utils import ( + "fmt" "reflect" + "unsafe" + "github.com/bytedance/sonic" "github.com/fatih/structtag" ) @@ -34,7 +37,7 @@ func NewTagChanger(replacer func(field reflect.StructField, tag reflect.StructTa } } -func (t *TagChanger) ReplaceTag(old reflect.Type) reflect.Type { +func (t *TagChanger) Replace(old reflect.Type) reflect.Type { if t.types == nil { t.types = make(map[reflect.Type]reflect.Type) } @@ -55,7 +58,7 @@ func (t *TagChanger) ReplaceTag(old reflect.Type) reflect.Type { // 处理map类型 if old.Kind() == reflect.Map { // 递归处理map的value类型 - nn := t.ReplaceTag(old.Elem()) + nn := t.Replace(old.Elem()) n := reflect.MapOf(old.Key(), nn) t.types[old] = n return n @@ -64,7 +67,7 @@ func (t *TagChanger) ReplaceTag(old reflect.Type) reflect.Type { // 处理slice/array类型 if old.Kind() == reflect.Slice || old.Kind() == reflect.Array { // 递归处理元素类型 - nn := t.ReplaceTag(old.Elem()) + nn := t.Replace(old.Elem()) n := reflect.SliceOf(nn) t.types[old] = n return n @@ -73,7 +76,7 @@ func (t *TagChanger) ReplaceTag(old reflect.Type) reflect.Type { // 处理指针类型 if old.Kind() == reflect.Ptr { // 递归处理指针指向的类型 - nn := t.ReplaceTag(old.Elem()) + nn := t.Replace(old.Elem()) n := reflect.PtrTo(nn) t.types[old] = n return n @@ -92,7 +95,7 @@ func (t *TagChanger) createFieldsWithReplacedTags(old reflect.Type) []reflect.St tag := oldField.Tag // 递归处理处理字段类型 - fieldType := t.ReplaceTag(oldField.Type) + fieldType := t.Replace(oldField.Type) // 使用自定义的replace函数替换tag newTag := t.replacer(oldField, tag) @@ -129,4 +132,29 @@ func ReplacerAddOmitemptyfunc(field reflect.StructField, tag reflect.StructTag) tags.Set(jsonTag) newTag := tags.String() return reflect.StructTag(newTag) -} \ No newline at end of file +} + +func MarshalWithTagChanger(t *TagChanger, v interface{}) ([]byte, error) { + // 先替换tag + oldType := reflect.TypeOf(v) + if oldType.Kind() != reflect.Ptr { + return nil, fmt.Errorf("v must be a pointer") + } + newType := t.Replace(oldType) + newObj := reflect.NewAt(newType, unsafe.Pointer(reflect.ValueOf(v).UnsafeAddr())).Interface() + + // 再序列化 + return sonic.Marshal(newObj) +} + +func UnmarshalWithTagChanger(t *TagChanger, data []byte, v interface{}) error { + // 先替换tag + oldType := reflect.TypeOf(v) + if oldType.Kind()!= reflect.Ptr { + return fmt.Errorf("v must be a pointer") + } + newType := t.Replace(oldType) + newObj := reflect.NewAt(newType, unsafe.Pointer(reflect.ValueOf(v).UnsafeAddr())).Interface() + // 再反序列化 + return sonic.Unmarshal(data, newObj) +} diff --git a/utils/tags_test.go b/utils/tags_test.go index a3f9d742a..39e767492 100644 --- a/utils/tags_test.go +++ b/utils/tags_test.go @@ -73,7 +73,7 @@ func TestTagChanger_ReplaceTag(t *testing.T) { return } fmt.Println(string(oldJs)) - got := tr.ReplaceTag(reflect.TypeOf(tt.args.old)) + got := tr.Replace(reflect.TypeOf(tt.args.old)) gotObj := reflect.NewAt(got, unsafe.Pointer(&oldObj)).Interface() js, err := json.Marshal(gotObj) println(string(js)) From d935c43c27d7c3e876d6da5110162520476660e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=B5=E4=BB=AA?= Date: Wed, 16 Apr 2025 21:30:56 +0800 Subject: [PATCH 3/4] fix --- utils/tags.go | 7 +++++-- utils/tags_test.go | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/utils/tags.go b/utils/tags.go index ae45f1d2b..ad51f390f 100644 --- a/utils/tags.go +++ b/utils/tags.go @@ -119,8 +119,11 @@ func ReplacerAddOmitemptyfunc(field reflect.StructField, tag reflect.StructTag) // 查找 json tag jsonTag, err := tags.Get("json") - if err != nil { - return reflect.StructTag(`json:",omitempty"`) + if jsonTag == nil { + if err := tags.Set(&structtag.Tag{Key: "json", Name: field.Name, Options: []string{"omitempty"}}); err != nil { + panic(err) + } + return reflect.StructTag(tags.String()) } // 修改 json tag 的值 diff --git a/utils/tags_test.go b/utils/tags_test.go index 39e767492..83914a994 100644 --- a/utils/tags_test.go +++ b/utils/tags_test.go @@ -27,9 +27,9 @@ import ( func TestTagChanger_ReplaceTag(t *testing.T) { type caseStruct struct { - A *int + A *int `xx:"yy"` B map[int]struct{ - C *int `json:"c"` + C *int `json:"c" xx:"yy"` } D []struct{ E *int `json:"e"` From c51ae9fa44bbb76e9ef3ca9d6d5286c5fde990be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=B5=E4=BB=AA?= Date: Mon, 21 Apr 2025 13:59:50 +0800 Subject: [PATCH 4/4] add test --- utils/tags.go | 75 +++++++++++++++++++------------ utils/tags_test.go | 110 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 143 insertions(+), 42 deletions(-) diff --git a/utils/tags.go b/utils/tags.go index ad51f390f..1de951e56 100644 --- a/utils/tags.go +++ b/utils/tags.go @@ -25,6 +25,9 @@ import ( "github.com/fatih/structtag" ) +// TagChanger is used to change the tag of a struct. +// +// WARNING: it is not thread-safe, must be used in a single thread or in initialization phase. type TagChanger struct { types map[reflect.Type]reflect.Type // old -> new replacer func(field reflect.StructField, tag reflect.StructTag) reflect.StructTag @@ -37,52 +40,45 @@ func NewTagChanger(replacer func(field reflect.StructField, tag reflect.StructTa } } +// Replace returns the type whose tag is replaced by replacer. +// +// WARNING: it is not thread-safe, must be used in a single thread or in initialization phase. func (t *TagChanger) Replace(old reflect.Type) reflect.Type { if t.types == nil { t.types = make(map[reflect.Type]reflect.Type) } - // 如果已经处理过该类型,直接返回 if n, ok := t.types[old]; ok { return n } - // 处理结构体类型 if old.Kind() == reflect.Struct { - // 创建一个新的结构体类型 newType := reflect.StructOf(t.createFieldsWithReplacedTags(old)) t.types[old] = newType return newType } - // 处理map类型 if old.Kind() == reflect.Map { - // 递归处理map的value类型 nn := t.Replace(old.Elem()) n := reflect.MapOf(old.Key(), nn) t.types[old] = n return n } - // 处理slice/array类型 if old.Kind() == reflect.Slice || old.Kind() == reflect.Array { - // 递归处理元素类型 nn := t.Replace(old.Elem()) n := reflect.SliceOf(nn) t.types[old] = n return n } - // 处理指针类型 if old.Kind() == reflect.Ptr { - // 递归处理指针指向的类型 nn := t.Replace(old.Elem()) n := reflect.PtrTo(nn) t.types[old] = n return n } - // 其他类型直接返回 return old } @@ -94,10 +90,8 @@ func (t *TagChanger) createFieldsWithReplacedTags(old reflect.Type) []reflect.St oldField := old.Field(i) tag := oldField.Tag - // 递归处理处理字段类型 fieldType := t.Replace(oldField.Type) - // 使用自定义的replace函数替换tag newTag := t.replacer(oldField, tag) fields[i] = reflect.StructField{ @@ -110,14 +104,13 @@ func (t *TagChanger) createFieldsWithReplacedTags(old reflect.Type) []reflect.St return fields } - -func ReplacerAddOmitemptyfunc(field reflect.StructField, tag reflect.StructTag) reflect.StructTag { +// ReplacerAddOmitempty is a replacer that adds "omitempty" to the json tag. +func ReplacerAddOmitempty(field reflect.StructField, tag reflect.StructTag) reflect.StructTag { tags, err := structtag.Parse(string(tag)) if err != nil { return tag } - // 查找 json tag jsonTag, err := tags.Get("json") if jsonTag == nil { if err := tags.Set(&structtag.Tag{Key: "json", Name: field.Name, Options: []string{"omitempty"}}); err != nil { @@ -126,38 +119,64 @@ func ReplacerAddOmitemptyfunc(field reflect.StructField, tag reflect.StructTag) return reflect.StructTag(tags.String()) } - // 修改 json tag 的值 if !jsonTag.HasOption("omitempty") { jsonTag.Options = append(jsonTag.Options, "omitempty") } - // 重新构建 tag tags.Set(jsonTag) newTag := tags.String() return reflect.StructTag(newTag) } -func MarshalWithTagChanger(t *TagChanger, v interface{}) ([]byte, error) { - // 先替换tag +func ReplaceerAddStringInt64(field reflect.StructField, tag reflect.StructTag) reflect.StructTag { + typ := field.Type + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + if typ.Kind() != reflect.Int64 && typ.Kind() != reflect.Uint64 { + return tag + } + + tags, err := structtag.Parse(string(tag)) + if err != nil { + return tag + } + + jsonTag, err := tags.Get("json") + if jsonTag == nil { + if err := tags.Set(&structtag.Tag{Key: "json", Name: field.Name, Options: []string{"string"}}); err != nil { + panic(err) + } + return reflect.StructTag(tags.String()) + } + + if !jsonTag.HasOption("string") { + jsonTag.Options = append(jsonTag.Options, "string") + } + + tags.Set(jsonTag) + newTag := tags.String() + return reflect.StructTag(newTag) +} + +// Marshal is used to marshal a struct whose tag is replaced by replacer into json. +func (t *TagChanger) Marshal(v interface{}) ([]byte, error) { oldType := reflect.TypeOf(v) if oldType.Kind() != reflect.Ptr { return nil, fmt.Errorf("v must be a pointer") } - newType := t.Replace(oldType) - newObj := reflect.NewAt(newType, unsafe.Pointer(reflect.ValueOf(v).UnsafeAddr())).Interface() - - // 再序列化 + newType := t.Replace(oldType.Elem()) + newObj := reflect.NewAt(newType, unsafe.Pointer(reflect.ValueOf(v).Pointer())).Interface() return sonic.Marshal(newObj) } -func UnmarshalWithTagChanger(t *TagChanger, data []byte, v interface{}) error { - // 先替换tag +// Unmarshal is used to unmarshal json into a struct whose tag is replaced by replacer. +func (t *TagChanger) Unmarshal(data []byte, v interface{}) error { oldType := reflect.TypeOf(v) if oldType.Kind()!= reflect.Ptr { return fmt.Errorf("v must be a pointer") } - newType := t.Replace(oldType) - newObj := reflect.NewAt(newType, unsafe.Pointer(reflect.ValueOf(v).UnsafeAddr())).Interface() - // 再反序列化 + newType := t.Replace(oldType.Elem()) + newObj := reflect.NewAt(newType, unsafe.Pointer(reflect.ValueOf(v).Pointer())).Interface() return sonic.Unmarshal(data, newObj) } diff --git a/utils/tags_test.go b/utils/tags_test.go index 83914a994..bef5f77af 100644 --- a/utils/tags_test.go +++ b/utils/tags_test.go @@ -27,22 +27,21 @@ import ( func TestTagChanger_ReplaceTag(t *testing.T) { type caseStruct struct { - A *int `xx:"yy"` - B map[int]struct{ + A *int `xx:"yy"` + B map[int]struct { C *int `json:"c" xx:"yy"` - } - D []struct{ + } + D []struct { E *int `json:"e"` } - F *struct{ + F *struct { G *int `json:"g"` } - H struct{ + H struct { I *int `json:"i"` } } - type fields struct { types map[reflect.Type]reflect.Type replacer func(field reflect.StructField, tag reflect.StructTag) reflect.StructTag @@ -51,9 +50,9 @@ func TestTagChanger_ReplaceTag(t *testing.T) { old interface{} } tests := []struct { - name string - args args - want string + name string + args args + want string }{ { name: "test1", @@ -65,10 +64,10 @@ func TestTagChanger_ReplaceTag(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tr := NewTagChanger(ReplacerAddOmitemptyfunc) + tr := NewTagChanger(ReplacerAddOmitempty) oldObj := caseStruct{} oldJs, err := json.Marshal(oldObj) - if err!= nil { + if err != nil { t.Errorf("TagChanger.ReplaceTag() error = %v", err) return } @@ -77,12 +76,95 @@ func TestTagChanger_ReplaceTag(t *testing.T) { gotObj := reflect.NewAt(got, unsafe.Pointer(&oldObj)).Interface() js, err := json.Marshal(gotObj) println(string(js)) - if err!= nil { + if err != nil { t.Errorf("TagChanger.ReplaceTag() error = %v", err) return - } else if string(js)!= tt.want { + } else if string(js) != tt.want { t.Errorf("TagChanger.ReplaceTag() = %v, want %v", string(js), tt.want) } }) } } + +func TestTagChanger_Marshal(t *testing.T) { + type args struct { + v interface{} + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + name: "1", + args: args{ + v: &struct { + A *int `json:"a"` + B *string + }{}, + }, + want: []byte(`{}`), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := NewTagChanger(ReplacerAddOmitempty) + got, err := tr.Marshal(tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("TagChanger.Marshal() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("TagChanger.Marshal() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTagChanger_Unmarshal(t *testing.T) { + type args struct { + v interface{} + } + type testStruct struct { + A int64 `json:"a"` + B uint64 + C *int64 + } + vc := int64(3) + tests := []struct { + name string + args args + js []byte + want interface{} + wantErr bool + }{ + { + name: "1", + args: args{ + v: &testStruct{}, + }, + js: []byte(`{"a":"1","b":"2","c":"3"}`), + want: &testStruct{ + A: 1, + B: 2, + C: &vc, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := NewTagChanger(ReplaceerAddStringInt64) + err := tr.Unmarshal(tt.js, tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("TagChanger.Marshal() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(tt.args.v, tt.want) { + t.Errorf("TagChanger.Marshal() = %v, want %v", tt.args.v, tt.want) + } + }) + } +}