diff --git a/go/marshaling.go b/go/marshaling.go index 8155967..84be263 100644 --- a/go/marshaling.go +++ b/go/marshaling.go @@ -4,6 +4,7 @@ import ( "reflect" "strings" + "github.com/samber/lo" "golang.org/x/exp/slices" ) @@ -57,7 +58,7 @@ func (d *Item) Unmarshal(i any) { } } - groups := make([]Item, 0, len(groupIDs)) + groups := make([]*Item, 0, len(groupIDs)) for _, g := range groupIDs { group := d.Group(g) groups = append(groups, group) @@ -104,6 +105,27 @@ func (d *Item) Unmarshal(i any) { continue } + // Tag + assignIf[Tag](vf, func() (Tag, bool) { + t := TagFrom(itf.Value) + if t == nil { + return Tag{}, false + } + return *t, true + }) + assignIf[[]Tag](vf, func() ([]Tag, bool) { + return TagsFrom(itf.Value), true + }) + assignIf[[]*Tag](vf, func() ([]*Tag, bool) { + return lo.ToSlicePtr(TagsFrom(itf.Value)), true + }) + + // Value + assignIf[Value](vf, func() (Value, bool) { + return *NewValue(itf.Value), true + }) + + // normal value itfv := reflect.ValueOf(itf.Value) if iftvt := reflect.TypeOf(itf.Value); iftvt != nil && iftvt.AssignableTo(vf.Type()) { vf.Set(itfv) @@ -113,17 +135,34 @@ func (d *Item) Unmarshal(i any) { } } -func Marshal(i any, item *Item) { - if item == nil || i == nil { +func assignIf[T any](vf reflect.Value, conv func() (T, bool)) { + var t T + if valueType := reflect.TypeOf(&t); vf.Type().AssignableTo(valueType) { + v, ok := conv() + if !ok { + return + } + vf.Set(reflect.ValueOf(lo.ToPtr(v))) + } else if valueType := reflect.TypeOf(t); vf.Type().AssignableTo(valueType) { + v, ok := conv() + if !ok { + return + } + vf.Set(reflect.ValueOf(v)) + } +} + +func Marshal(src any, item *Item) { + if item == nil || src == nil { return } - t := reflect.TypeOf(i) + t := reflect.TypeOf(src) if t == nil { return } - v := reflect.ValueOf(i) + v := reflect.ValueOf(src) if t.Kind() == reflect.Pointer { if v.IsNil() { return @@ -140,6 +179,7 @@ func Marshal(i any, item *Item) { f := t.Field(i) tag := f.Tag.Get(tag) key, opts, _ := strings.Cut(tag, ",") + if key == "" || key == "-" { continue } @@ -157,7 +197,9 @@ func Marshal(i any, item *Item) { vft := vf.Type() var value any - if vft.Kind() == reflect.Slice && vft.Elem().Kind() == reflect.String && vf.Len() > 0 { + if m, ok := vf.Interface().(MarshalCMS); ok { + value = m.MarshalCMS() + } else 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++ { diff --git a/go/marshaling_test.go b/go/marshaling_test.go index d4b55a4..0b2209e 100644 --- a/go/marshaling_test.go +++ b/go/marshaling_test.go @@ -24,6 +24,8 @@ func TestItem_Unmarshal(t *testing.T) { GGG []*G `cms:"ggg,group"` HHH []G `cms:"hhh,group"` III *int `cms:"iii,,metadata,includezero"` + JJJ []Tag `cms:"jjj"` + KKK *Value `cms:"kkk"` } s := S{} @@ -38,6 +40,8 @@ func TestItem_Unmarshal(t *testing.T) { {Key: "hhh", Type: "group", Value: []string{"1"}}, {Key: "aaa", Group: "1", Value: "123"}, {Key: "iii"}, + {Key: "jjj", Value: []any{map[string]any{"id": "xxx", "name": "tag"}}}, + {Key: "kkk", Value: []any{map[string]any{"id": "xxx", "name": "tag"}}}, }, MetadataFields: []*Field{ {Key: "eee", Value: true}, @@ -54,6 +58,8 @@ func TestItem_Unmarshal(t *testing.T) { GGG: []*G{{ID: "1", AAA: "123"}, {ID: "2"}}, HHH: []G{{ID: "1", AAA: "123"}}, III: nil, + JJJ: []Tag{{ID: "xxx", Name: "tag"}}, + KKK: &Value{value: []any{map[string]any{"id": "xxx", "name": "tag"}}}, }, s) // no panic @@ -80,6 +86,8 @@ func TestMarshal(t *testing.T) { GGG []G `cms:"ggg"` HHH []*G `cms:"hhh"` III *int `cms:"iii,,metadata,includezero"` + JJJ *Value `cms:"jjj"` + KKK *Tag `cms:"kkk"` } s := S{ @@ -91,6 +99,8 @@ func TestMarshal(t *testing.T) { FFF: true, GGG: []G{{ID: "1", AAA: "ggg"}}, HHH: []*G{{ID: "2", AAA: "hhh"}, nil}, + JJJ: &Value{value: "foo"}, + KKK: &Tag{ID: "tag"}, } expected := &Item{ @@ -105,6 +115,8 @@ func TestMarshal(t *testing.T) { {Key: "ggg", Type: "group", Value: []string{"1"}}, {Key: "aaa", Group: "2", Type: "text", Value: "hhh"}, {Key: "hhh", Type: "group", Value: []string{"2"}}, + {Key: "jjj", Type: "", Value: "foo"}, + {Key: "kkk", Type: "", Value: "tag"}, }, MetadataFields: []*Field{ {Key: "fff", Type: "bool", Value: true}, diff --git a/go/model.go b/go/model.go index 6c77b5c..465d64d 100644 --- a/go/model.go +++ b/go/model.go @@ -187,7 +187,7 @@ func (i *Item) MetadataFieldByKeyAndGroup(key, group string) *Field { return nil } -func (i *Item) Group(g string) Item { +func (i *Item) Group(g string) *Item { fields := lo.Map(lo.Filter(i.Fields, func(f *Field, _ int) bool { return f.Group == g }), func(f *Field, _ int) *Field { @@ -204,7 +204,7 @@ func (i *Item) Group(g string) Item { return g }) - return Item{ + return &Item{ ID: g, ModelID: i.ModelID, Fields: fields, diff --git a/go/model_test.go b/go/model_test.go index 6205b15..85cec81 100644 --- a/go/model_test.go +++ b/go/model_test.go @@ -25,7 +25,7 @@ func TestItem_Group(t *testing.T) { } g := item.Group("1") - assert.Equal(t, Item{ + assert.Equal(t, &Item{ ID: "1", ModelID: "xxx", Fields: []*Field{ diff --git a/go/value.go b/go/value.go index eac1069..4409d90 100644 --- a/go/value.go +++ b/go/value.go @@ -7,6 +7,10 @@ import ( "github.com/samber/lo" ) +type MarshalCMS interface { + MarshalCMS() any +} + type Tag struct { ID string `json:"id"` Name string `json:"name"` @@ -32,6 +36,33 @@ func TagFrom(j any) *Tag { return &t } +func TagsFrom(j any) []Tag { + s, ok := j.([]any) + if !ok { + s2, ok := j.([]map[string]any) + if !ok { + return nil + } + s = make([]any, len(s2)) + for i, e := range s2 { + s[i] = e + } + } + + res := make([]Tag, len(s)) + for i, e := range s { + if t := TagFrom(e); t != nil { + res[i] = *t + } + } + + return res +} + +func (t Tag) MarshalCMS() any { + return t.ID +} + type Value struct { value any } @@ -87,27 +118,10 @@ func (v *Value) Tag() *Tag { } func (v *Value) Tags() []Tag { - values, ok := v.value.([]any) - if !ok { - values2, ok := v.value.([]map[string]any) - if !ok { - return nil - } - - values = make([]any, len(values2)) - for i, value := range values2 { - values[i] = value - } - } - - res := make([]Tag, len(values)) - for i, value := range values { - if t := TagFrom(value); t != nil { - res[i] = *t - } + if v == nil { + return nil } - - return res + return TagsFrom(v.value) } func (f *Value) JSON(j any) error { @@ -169,6 +183,33 @@ func getValue[T any](v *Value) *T { return nil } +func (v *Value) MarshalCMS() any { + if v == nil { + return nil + } + if m, ok := v.value.(MarshalCMS); ok { + return m.MarshalCMS() + } + return v.value +} + +func (v *Value) MarshalJSON() ([]byte, error) { + if v == nil { + return nil, nil + } + return json.Marshal(v.value) +} + +func (v *Value) UnmarshalJSON(b []byte) error { + if v == nil { + *v = Value{} + } + if err := json.Unmarshal(b, &v.value); err != nil { + return err + } + return nil +} + func getValues[T any](v *Value) []T { if v == nil { return nil