From e882c68ee4405ea2d51cce0c7661826fa6c43647 Mon Sep 17 00:00:00 2001 From: rot1024 Date: Fri, 22 Dec 2023 11:24:46 +0900 Subject: [PATCH] fix(go): NewValue typo --- go/marshaling.go | 255 ++++++++++++++++++++++++++++++++++++++++++ go/marshaling_test.go | 128 +++++++++++++++++++++ go/model.go | 249 ----------------------------------------- go/model_test.go | 121 -------------------- go/model_value.go | 2 +- 5 files changed, 384 insertions(+), 371 deletions(-) create mode 100644 go/marshaling.go create mode 100644 go/marshaling_test.go diff --git a/go/marshaling.go b/go/marshaling.go new file mode 100644 index 0000000..8155967 --- /dev/null +++ b/go/marshaling.go @@ -0,0 +1,255 @@ +package cms + +import ( + "reflect" + "strings" + + "golang.org/x/exp/slices" +) + +func (d *Item) Unmarshal(i any) { + if i == nil { + return + } + + v := reflect.ValueOf(i) + if v.IsNil() { + return + } + + v = v.Elem() + t := v.Type() + + if t.Kind() != reflect.Struct { + return + } + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + tag := f.Tag.Get(tag) + key, opts, _ := strings.Cut(tag, ",") + if key == "" || key == "-" { + continue + } + + isMetadata := strings.HasSuffix(opts, ",metadata") + vf := v.FieldByName(f.Name) + + if key == "id" { + if f.Type.Kind() == reflect.String { + vf.SetString(d.ID) + } + continue + } + + var itf *Field + if isMetadata { + itf = d.MetadataFieldByKey(key) + } else { + itf = d.FieldByKey(key) + } + + if itf != nil && itf.Type == "group" { + groupIDs := itf.GetValue().Strings() + if len(groupIDs) == 0 { + if groupID := itf.GetValue().String(); groupID != nil { + groupIDs = []string{*groupID} + } + } + + groups := make([]Item, 0, len(groupIDs)) + for _, g := range groupIDs { + group := d.Group(g) + groups = append(groups, group) + } + + if len(groups) == 0 { + continue + } + + if f.Type.Kind() == reflect.Slice && + (f.Type.Elem().Kind() == reflect.Struct || + f.Type.Elem().Kind() == reflect.Ptr && + f.Type.Elem().Elem().Kind() == reflect.Struct) { + s := reflect.MakeSlice(f.Type, 0, len(groups)) + isPointer := f.Type.Elem().Kind() == reflect.Ptr + + for _, g := range groups { + var rv reflect.Value + if isPointer { + rv = reflect.New(f.Type.Elem().Elem()) + } else { + rv = reflect.New(f.Type.Elem()) + } + + i := rv.Interface() + g.Unmarshal(i) + + if isPointer { + s = reflect.Append(s, rv) + } else { + s = reflect.Append(s, rv.Elem()) + } + } + + vf.Set(s) + } else if f.Type.Kind() == reflect.Struct { + groups[0].Unmarshal(vf.Addr().Interface()) + } else if f.Type.Kind() == reflect.Pointer && f.Type.Elem().Kind() == reflect.Struct { + groups[0].Unmarshal(vf.Interface()) + } + } + + if itf == nil || itf.Value == nil || !vf.CanSet() { + continue + } + + itfv := reflect.ValueOf(itf.Value) + if iftvt := reflect.TypeOf(itf.Value); iftvt != nil && iftvt.AssignableTo(vf.Type()) { + vf.Set(itfv) + } else if itfv.CanConvert(f.Type) { + vf.Set(itfv.Convert(f.Type)) + } + } +} + +func Marshal(i any, item *Item) { + if item == nil || i == nil { + return + } + + t := reflect.TypeOf(i) + if t == nil { + return + } + + v := reflect.ValueOf(i) + if t.Kind() == reflect.Pointer { + if v.IsNil() { + return + } + t = t.Elem() + v = v.Elem() + } + if t.Kind() != reflect.Struct { + return + } + + ni := Item{} + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + tag := f.Tag.Get(tag) + key, opts, _ := strings.Cut(tag, ",") + if key == "" || key == "-" { + continue + } + + ty, optsRemaining, _ := strings.Cut(opts, ",") + optsSplited := strings.Split(optsRemaining, ",") + omitempty := !slices.Contains(optsSplited, "includezero") + isMetadata := slices.Contains(optsSplited, "metadata") + + vf := v.FieldByName(f.Name) + if key == "id" { + ni.ID, _ = vf.Interface().(string) + continue + } + + vft := vf.Type() + var value any + if vft.Kind() == reflect.Slice && vft.Elem().Kind() == reflect.String && vf.Len() > 0 { + st := reflect.TypeOf("") + v := make([]string, 0, vf.Len()) + for i := 0; i < cap(v); i++ { + vfs := vf.Index(i).Convert(st) + v = append(v, vfs.String()) + } + value = v + } else if vft.Kind() == reflect.Slice && vf.Len() > 0 && (vft.Elem().Kind() == reflect.Struct || + vft.Elem().Kind() == reflect.Ptr && vft.Elem().Elem().Kind() == reflect.Struct) { + isPointer := vft.Elem().Kind() == reflect.Ptr + + v := make([]string, 0, vf.Len()) + for i := 0; i < cap(v); i++ { + var in any + if isPointer { + in = vf.Index(i).Interface() + } else { + in = vf.Index(i).Addr().Interface() + } + + item := Item{} + Marshal(in, &item) + if item.ID == "" { + continue + } + + // assign group + for i := range item.Fields { + item.Fields[i].Group = item.ID + } + for i := range item.MetadataFields { + item.Fields[i].Group = item.ID + } + + // merge i to ni + ni.Fields = append(ni.Fields, item.Fields...) + ni.MetadataFields = append(ni.MetadataFields, item.MetadataFields...) + + v = append(v, item.ID) + } + + if len(v) > 0 { + value = v + ty = "group" + } + } else if vft.Kind() == reflect.Struct || vft.Kind() == reflect.Ptr && vft.Elem().Kind() == reflect.Struct { + isPointer := vft.Kind() == reflect.Ptr + var v any + if isPointer { + v = vf.Interface() + } else { + v = vf.Addr().Interface() + } + + item := Item{} + Marshal(v, &item) + if item.ID == "" { + continue + } + + // assign group + for i := range item.Fields { + item.Fields[i].Group = item.ID + } + for i := range item.MetadataFields { + item.Fields[i].Group = item.ID + } + + // merge i to ni + ni.Fields = append(ni.Fields, item.Fields...) + ni.MetadataFields = append(ni.MetadataFields, item.MetadataFields...) + + value = item.ID + ty = "group" + } else if !omitempty || !vf.IsZero() { + value = vf.Convert(vft).Interface() + } + + if value != nil { + f := &Field{ + Key: key, + Type: ty, + Value: value, + } + + if isMetadata { + ni.MetadataFields = append(ni.MetadataFields, f) + } else { + ni.Fields = append(ni.Fields, f) + } + } + } + + *item = ni +} diff --git a/go/marshaling_test.go b/go/marshaling_test.go new file mode 100644 index 0000000..d4b55a4 --- /dev/null +++ b/go/marshaling_test.go @@ -0,0 +1,128 @@ +package cms + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestItem_Unmarshal(t *testing.T) { + type str string + + type G struct { + ID string `cms:"id"` + AAA string `cms:"aaa,text"` + } + + type S struct { + ID string `cms:"id"` + AAA str `cms:"aaa,"` + BBB []string `cms:"bbb"` + CCC []str `cms:"ccc"` + DDD map[string]any `cms:"ddd"` + EEE bool `cms:"eee,,metadata"` + GGG []*G `cms:"ggg,group"` + HHH []G `cms:"hhh,group"` + III *int `cms:"iii,,metadata,includezero"` + } + s := S{} + + (&Item{ + ID: "xxx", + Fields: []*Field{ + {Key: "aaa", Value: str("bbb")}, + {Key: "bbb", Value: []string{"ccc", "bbb"}}, + {Key: "ccc", Value: []str{"a", "b"}}, + {Key: "ddd", Value: map[string]any{"a": "b"}}, + {Key: "ggg", Type: "group", Value: []string{"1", "2"}}, + {Key: "hhh", Type: "group", Value: []string{"1"}}, + {Key: "aaa", Group: "1", Value: "123"}, + {Key: "iii"}, + }, + MetadataFields: []*Field{ + {Key: "eee", Value: true}, + }, + }).Unmarshal(&s) + + assert.Equal(t, S{ + ID: "xxx", + AAA: str("bbb"), + BBB: []string{"ccc", "bbb"}, + CCC: []str{"a", "b"}, + DDD: map[string]any{"a": "b"}, + EEE: true, + GGG: []*G{{ID: "1", AAA: "123"}, {ID: "2"}}, + HHH: []G{{ID: "1", AAA: "123"}}, + III: nil, + }, s) + + // no panic + (&Item{}).Unmarshal(nil) + (&Item{}).Unmarshal((*S)(nil)) +} + +func TestMarshal(t *testing.T) { + type str string + + type G struct { + ID string `cms:"id"` + AAA string `cms:"aaa,text"` + } + + type S struct { + ID string `cms:"id"` + AAA string `cms:"aaa,text"` + BBB []string `cms:"bbb,select"` + CCC str `cms:"ccc"` + DDD []str `cms:"ddd"` + EEE string `cms:"eee,text"` + FFF bool `cms:"fff,bool,metadata"` + GGG []G `cms:"ggg"` + HHH []*G `cms:"hhh"` + III *int `cms:"iii,,metadata,includezero"` + } + + s := S{ + ID: "xxx", + AAA: "bbb", + BBB: []string{"ccc", "bbb"}, + CCC: str("x"), + DDD: []str{"1", "2"}, + FFF: true, + GGG: []G{{ID: "1", AAA: "ggg"}}, + HHH: []*G{{ID: "2", AAA: "hhh"}, nil}, + } + + expected := &Item{ + ID: "xxx", + Fields: []*Field{ + {Key: "aaa", Type: "text", Value: "bbb"}, + {Key: "bbb", Type: "select", Value: []string{"ccc", "bbb"}}, + {Key: "ccc", Type: "", Value: str("x")}, + {Key: "ddd", Type: "", Value: []string{"1", "2"}}, + // no field for eee + {Key: "aaa", Group: "1", Type: "text", Value: "ggg"}, + {Key: "ggg", Type: "group", Value: []string{"1"}}, + {Key: "aaa", Group: "2", Type: "text", Value: "hhh"}, + {Key: "hhh", Type: "group", Value: []string{"2"}}, + }, + MetadataFields: []*Field{ + {Key: "fff", Type: "bool", Value: true}, + {Key: "iii", Type: "", Value: (*int)(nil)}, + }, + } + + item := &Item{} + Marshal(s, item) + assert.Equal(t, expected, item) + + item2 := &Item{} + Marshal(&s, item2) + assert.Equal(t, item, item2) + + // no panic + Marshal(nil, nil) + Marshal(nil, item2) + Marshal((*S)(nil), item2) + Marshal(s, nil) +} diff --git a/go/model.go b/go/model.go index 31060db..6c77b5c 100644 --- a/go/model.go +++ b/go/model.go @@ -1,8 +1,6 @@ package cms import ( - "reflect" - "strings" "time" "github.com/samber/lo" @@ -214,253 +212,6 @@ func (i *Item) Group(g string) Item { } } -func (d *Item) Unmarshal(i any) { - if i == nil { - return - } - - v := reflect.ValueOf(i) - if v.IsNil() { - return - } - - v = v.Elem() - t := v.Type() - - if t.Kind() != reflect.Struct { - return - } - - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - tag := f.Tag.Get(tag) - key, opts, _ := strings.Cut(tag, ",") - if key == "" || key == "-" { - continue - } - - isMetadata := strings.HasSuffix(opts, ",metadata") - vf := v.FieldByName(f.Name) - - if key == "id" { - if f.Type.Kind() == reflect.String { - vf.SetString(d.ID) - } - continue - } - - var itf *Field - if isMetadata { - itf = d.MetadataFieldByKey(key) - } else { - itf = d.FieldByKey(key) - } - - if itf != nil && itf.Type == "group" { - groupIDs := itf.GetValue().Strings() - if len(groupIDs) == 0 { - if groupID := itf.GetValue().String(); groupID != nil { - groupIDs = []string{*groupID} - } - } - - groups := make([]Item, 0, len(groupIDs)) - for _, g := range groupIDs { - group := d.Group(g) - groups = append(groups, group) - } - - if len(groups) == 0 { - continue - } - - if f.Type.Kind() == reflect.Slice && - (f.Type.Elem().Kind() == reflect.Struct || - f.Type.Elem().Kind() == reflect.Ptr && - f.Type.Elem().Elem().Kind() == reflect.Struct) { - s := reflect.MakeSlice(f.Type, 0, len(groups)) - isPointer := f.Type.Elem().Kind() == reflect.Ptr - - for _, g := range groups { - var rv reflect.Value - if isPointer { - rv = reflect.New(f.Type.Elem().Elem()) - } else { - rv = reflect.New(f.Type.Elem()) - } - - i := rv.Interface() - g.Unmarshal(i) - - if isPointer { - s = reflect.Append(s, rv) - } else { - s = reflect.Append(s, rv.Elem()) - } - } - - vf.Set(s) - } else if f.Type.Kind() == reflect.Struct { - groups[0].Unmarshal(vf.Addr().Interface()) - } else if f.Type.Kind() == reflect.Pointer && f.Type.Elem().Kind() == reflect.Struct { - groups[0].Unmarshal(vf.Interface()) - } - } - - if itf == nil || itf.Value == nil || !vf.CanSet() { - continue - } - - itfv := reflect.ValueOf(itf.Value) - if iftvt := reflect.TypeOf(itf.Value); iftvt != nil && iftvt.AssignableTo(vf.Type()) { - vf.Set(itfv) - } else if itfv.CanConvert(f.Type) { - vf.Set(itfv.Convert(f.Type)) - } - } -} - -func Marshal(i any, item *Item) { - if item == nil || i == nil { - return - } - - t := reflect.TypeOf(i) - if t == nil { - return - } - - v := reflect.ValueOf(i) - if t.Kind() == reflect.Pointer { - if v.IsNil() { - return - } - t = t.Elem() - v = v.Elem() - } - if t.Kind() != reflect.Struct { - return - } - - ni := Item{} - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - tag := f.Tag.Get(tag) - key, opts, _ := strings.Cut(tag, ",") - if key == "" || key == "-" { - continue - } - - ty, optsRemaining, _ := strings.Cut(opts, ",") - optsSplited := strings.Split(optsRemaining, ",") - omitempty := !slices.Contains(optsSplited, "includezero") - isMetadata := slices.Contains(optsSplited, "metadata") - - vf := v.FieldByName(f.Name) - if key == "id" { - ni.ID, _ = vf.Interface().(string) - continue - } - - vft := vf.Type() - var value any - if vft.Kind() == reflect.Slice && vft.Elem().Kind() == reflect.String && vf.Len() > 0 { - st := reflect.TypeOf("") - v := make([]string, 0, vf.Len()) - for i := 0; i < cap(v); i++ { - vfs := vf.Index(i).Convert(st) - v = append(v, vfs.String()) - } - value = v - } else if vft.Kind() == reflect.Slice && vf.Len() > 0 && (vft.Elem().Kind() == reflect.Struct || - vft.Elem().Kind() == reflect.Ptr && vft.Elem().Elem().Kind() == reflect.Struct) { - isPointer := vft.Elem().Kind() == reflect.Ptr - - v := make([]string, 0, vf.Len()) - for i := 0; i < cap(v); i++ { - var in any - if isPointer { - in = vf.Index(i).Interface() - } else { - in = vf.Index(i).Addr().Interface() - } - - item := Item{} - Marshal(in, &item) - if item.ID == "" { - continue - } - - // assign group - for i := range item.Fields { - item.Fields[i].Group = item.ID - } - for i := range item.MetadataFields { - item.Fields[i].Group = item.ID - } - - // merge i to ni - ni.Fields = append(ni.Fields, item.Fields...) - ni.MetadataFields = append(ni.MetadataFields, item.MetadataFields...) - - v = append(v, item.ID) - } - - if len(v) > 0 { - value = v - ty = "group" - } - } else if vft.Kind() == reflect.Struct || vft.Kind() == reflect.Ptr && vft.Elem().Kind() == reflect.Struct { - isPointer := vft.Kind() == reflect.Ptr - var v any - if isPointer { - v = vf.Interface() - } else { - v = vf.Addr().Interface() - } - - item := Item{} - Marshal(v, &item) - if item.ID == "" { - continue - } - - // assign group - for i := range item.Fields { - item.Fields[i].Group = item.ID - } - for i := range item.MetadataFields { - item.Fields[i].Group = item.ID - } - - // merge i to ni - ni.Fields = append(ni.Fields, item.Fields...) - ni.MetadataFields = append(ni.MetadataFields, item.MetadataFields...) - - value = item.ID - ty = "group" - } else if !omitempty || !vf.IsZero() { - value = vf.Convert(vft).Interface() - } - - if value != nil { - f := &Field{ - Key: key, - Type: ty, - Value: value, - } - - if isMetadata { - ni.MetadataFields = append(ni.MetadataFields, f) - } else { - ni.Fields = append(ni.Fields, f) - } - } - } - - *item = ni -} - type Field struct { ID string `json:"id,omitempty"` Key string `json:"key,omitempty"` diff --git a/go/model_test.go b/go/model_test.go index d345eae..3bfd9a2 100644 --- a/go/model_test.go +++ b/go/model_test.go @@ -35,127 +35,6 @@ func TestItem_Group(t *testing.T) { }, g) } -func TestItem_Unmarshal(t *testing.T) { - type str string - - type G struct { - ID string `cms:"id"` - AAA string `cms:"aaa,text"` - } - - type S struct { - ID string `cms:"id"` - AAA str `cms:"aaa,"` - BBB []string `cms:"bbb"` - CCC []str `cms:"ccc"` - DDD map[string]any `cms:"ddd"` - EEE bool `cms:"eee,,metadata"` - GGG []*G `cms:"ggg,group"` - HHH []G `cms:"hhh,group"` - III *int `cms:"iii,,metadata,includezero"` - } - s := S{} - - (&Item{ - ID: "xxx", - Fields: []*Field{ - {Key: "aaa", Value: str("bbb")}, - {Key: "bbb", Value: []string{"ccc", "bbb"}}, - {Key: "ccc", Value: []str{"a", "b"}}, - {Key: "ddd", Value: map[string]any{"a": "b"}}, - {Key: "ggg", Type: "group", Value: []string{"1", "2"}}, - {Key: "hhh", Type: "group", Value: []string{"1"}}, - {Key: "aaa", Group: "1", Value: "123"}, - {Key: "iii"}, - }, - MetadataFields: []*Field{ - {Key: "eee", Value: true}, - }, - }).Unmarshal(&s) - - assert.Equal(t, S{ - ID: "xxx", - AAA: str("bbb"), - BBB: []string{"ccc", "bbb"}, - CCC: []str{"a", "b"}, - DDD: map[string]any{"a": "b"}, - EEE: true, - GGG: []*G{{ID: "1", AAA: "123"}, {ID: "2"}}, - HHH: []G{{ID: "1", AAA: "123"}}, - III: nil, - }, s) - - // no panic - (&Item{}).Unmarshal(nil) - (&Item{}).Unmarshal((*S)(nil)) -} - -func TestMarshal(t *testing.T) { - type str string - - type G struct { - ID string `cms:"id"` - AAA string `cms:"aaa,text"` - } - - type S struct { - ID string `cms:"id"` - AAA string `cms:"aaa,text"` - BBB []string `cms:"bbb,select"` - CCC str `cms:"ccc"` - DDD []str `cms:"ddd"` - EEE string `cms:"eee,text"` - FFF bool `cms:"fff,bool,metadata"` - GGG []G `cms:"ggg"` - HHH []*G `cms:"hhh"` - III *int `cms:"iii,,metadata,includezero"` - } - - s := S{ - ID: "xxx", - AAA: "bbb", - BBB: []string{"ccc", "bbb"}, - CCC: str("x"), - DDD: []str{"1", "2"}, - FFF: true, - GGG: []G{{ID: "1", AAA: "ggg"}}, - HHH: []*G{{ID: "2", AAA: "hhh"}, nil}, - } - - expected := &Item{ - ID: "xxx", - Fields: []*Field{ - {Key: "aaa", Type: "text", Value: "bbb"}, - {Key: "bbb", Type: "select", Value: []string{"ccc", "bbb"}}, - {Key: "ccc", Type: "", Value: str("x")}, - {Key: "ddd", Type: "", Value: []string{"1", "2"}}, - // no field for eee - {Key: "aaa", Group: "1", Type: "text", Value: "ggg"}, - {Key: "ggg", Type: "group", Value: []string{"1"}}, - {Key: "aaa", Group: "2", Type: "text", Value: "hhh"}, - {Key: "hhh", Type: "group", Value: []string{"2"}}, - }, - MetadataFields: []*Field{ - {Key: "fff", Type: "bool", Value: true}, - {Key: "iii", Type: "", Value: (*int)(nil)}, - }, - } - - item := &Item{} - Marshal(s, item) - assert.Equal(t, expected, item) - - item2 := &Item{} - Marshal(&s, item2) - assert.Equal(t, item, item2) - - // no panic - Marshal(nil, nil) - Marshal(nil, item2) - Marshal((*S)(nil), item2) - Marshal(s, nil) -} - func TestItem_Field(t *testing.T) { assert.Equal(t, &Field{ ID: "bbb", Value: "ccc", Type: "string", diff --git a/go/model_value.go b/go/model_value.go index 7a43644..ade0b36 100644 --- a/go/model_value.go +++ b/go/model_value.go @@ -10,7 +10,7 @@ type Value struct { value any } -func NewValeu(value any) *Value { +func NewValue(value any) *Value { return &Value{value: value} }