Skip to content

Commit

Permalink
fix(go): NewValue typo
Browse files Browse the repository at this point in the history
  • Loading branch information
rot1024 committed Dec 22, 2023
1 parent 56fbd1f commit e882c68
Show file tree
Hide file tree
Showing 5 changed files with 384 additions and 371 deletions.
255 changes: 255 additions & 0 deletions go/marshaling.go
Original file line number Diff line number Diff line change
@@ -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
}
128 changes: 128 additions & 0 deletions go/marshaling_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit e882c68

Please sign in to comment.