Thanks to visit codestin.com
Credit goes to Github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

The structure and content of this file follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.26.11] - 2025-11-03
### Fixed
- Fixed recomposing nested anonymous structs.

## [1.26.10] - 2025-08-30
### Fixed
- Fixed float parsing issues.
Expand Down
2 changes: 1 addition & 1 deletion alt/alt.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func MustNewRecomposer(
}
for v, fun := range composers {
rt := reflect.TypeOf(v)
if _, err := r.registerComposer(rt, fun); err != nil {
if _, err := r.registerComposer(rt, fun, ""); err != nil {
panic(err)
}
}
Expand Down
73 changes: 48 additions & 25 deletions alt/recomposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func init() {
// RegisterComposer regsiters a composer function for a value type. A nil
// function will still register the default composer which uses reflection.
func (r *Recomposer) RegisterComposer(val any, fun RecomposeFunc) error {
_, err := r.registerComposer(reflect.TypeOf(val), fun)
_, err := r.registerComposer(reflect.TypeOf(val), fun, "")

return err
}
Expand All @@ -77,20 +77,25 @@ func (r *Recomposer) RegisterUnmarshalerComposer(fun RecomposeAnyFunc) {
}
}

func (r *Recomposer) registerComposer(rt reflect.Type, fun RecomposeFunc) (*composer, error) {
func (r *Recomposer) registerComposer(rt reflect.Type, fun RecomposeFunc, typeName string) (*composer, error) {
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
full := rt.PkgPath() + "/" + rt.Name()
// TBD could loosen this up and allow any type as long as a function is provided.
name := rt.Name()
if len(name) == 0 {
name = typeName
}
full := rt.PkgPath() + "/" + name

// TBD could loosen this up and allow any type as long as a function is provided. id is path through field names
if rt.Kind() != reflect.Struct {
return nil, fmt.Errorf("only structs can be recomposed. %s is not a struct type", rt)
}
c := r.composers[full]
if c == nil {
c = &composer{
fun: fun,
short: rt.Name(),
short: name,
full: full,
rtype: rt,
}
Expand All @@ -115,10 +120,21 @@ func (r *Recomposer) registerComposer(rt reflect.Type, fun RecomposeFunc) (*comp
case reflect.Array, reflect.Slice, reflect.Map, reflect.Ptr:
ft = ft.Elem()
}
if _, has := r.composers[ft.Name()]; has {
fname := ft.Name()
if len(fname) == 0 {
if len(typeName) == 0 {
typeName = rt.Name()
}
fname = typeName + "." + f.Name
}
if _, has := r.composers[fname]; has {
continue
}
_, _ = r.registerComposer(ft, nil)
if 0 < len(ft.Name()) {
_, _ = r.registerComposer(ft, nil, "")
} else {
_, _ = r.registerComposer(ft, nil, fname)
}
}
return c, nil
}
Expand Down Expand Up @@ -177,12 +193,12 @@ func (r *Recomposer) MustRecompose(v any, tv ...any) (out any) {
switch rv.Kind() {
case reflect.Array, reflect.Slice:
rv = reflect.New(rv.Type())
r.recomp(v, rv)
r.recomp(v, rv, "")
out = rv.Elem().Interface()
case reflect.Map:
r.recomp(v, rv)
r.recomp(v, rv, "")
case reflect.Ptr:
r.recomp(v, rv)
r.recomp(v, rv, "")
switch rv.Elem().Kind() {
case reflect.Slice, reflect.Array, reflect.Map, reflect.Interface:
out = rv.Elem().Interface()
Expand Down Expand Up @@ -241,7 +257,7 @@ func (r *Recomposer) recompAny(v any) any {
return val
}
rv := reflect.New(c.rtype)
r.recomp(v, rv)
r.recomp(v, rv, "")
return rv.Interface()
}
}
Expand Down Expand Up @@ -283,7 +299,7 @@ func (r *Recomposer) recompAny(v any) any {
return val
}
rv := reflect.New(c.rtype)
r.recomp(simple, rv)
r.recomp(simple, rv, "")
return rv.Interface()
}
}
Expand All @@ -309,7 +325,7 @@ func (r *Recomposer) recompAny(v any) any {
return v
}

func (r *Recomposer) recomp(v any, rv reflect.Value) {
func (r *Recomposer) recomp(v any, rv reflect.Value, typeName string) {
as, _ := rv.Interface().(AttrSetter)
if rv.Kind() == reflect.Ptr {
if v == nil {
Expand Down Expand Up @@ -337,12 +353,12 @@ func (r *Recomposer) recomp(v any, rv reflect.Value) {
et = et.Elem()
for i := 0; i < size; i++ {
ev := reflect.New(et)
r.recomp(va[i], ev)
r.recomp(va[i], ev, "")
av.Index(i).Set(ev)
}
} else {
for i := 0; i < size; i++ {
r.setValue(va[i], av.Index(i), nil)
r.setValue(va[i], av.Index(i), nil, "")
}
}
rv.Set(av)
Expand All @@ -361,7 +377,7 @@ func (r *Recomposer) recomp(v any, rv reflect.Value) {
// actual type of the element value if the slice input is []any.
ev := vv.Index(i).Interface()
ri := rv.Index(i)
r.setValue(ev, ri, nil)
r.setValue(ev, ri, nil, "")
}
case reflect.Map:
if v == nil {
Expand Down Expand Up @@ -393,20 +409,24 @@ func (r *Recomposer) recomp(v any, rv reflect.Value) {
et = et.Elem()
for k, m := range vm {
ev := reflect.New(et)
r.recomp(m, ev)
r.recomp(m, ev, "")
rv.SetMapIndex(reflect.ValueOf(k), ev)
}
default:
for k, m := range vm {
ev := reflect.New(et)
r.recomp(m, ev)
r.recomp(m, ev, "")
rv.SetMapIndex(reflect.ValueOf(k), ev.Elem())
}
}
case reflect.Struct:
vm, ok := (v).(map[string]any)
rt := rv.Type()
if len(typeName) == 0 || 0 < len(rt.Name()) {
typeName = rt.Name()
}
if !ok {
if c := r.composers[rv.Type().Name()]; c != nil && c.any != nil {
if c := r.composers[rt.Name()]; c != nil && c.any != nil {
if val, err := c.any(v); err == nil {
if val == nil {
break
Expand Down Expand Up @@ -444,7 +464,7 @@ func (r *Recomposer) recomp(v any, rv reflect.Value) {
return
}
var im map[string]reflect.StructField
if c := r.composers[rv.Type().Name()]; c != nil {
if c := r.composers[typeName]; c != nil {
if c.fun != nil {
if val, err := c.fun(vm); err == nil {
vv := reflect.ValueOf(val)
Expand All @@ -459,7 +479,7 @@ func (r *Recomposer) recomp(v any, rv reflect.Value) {
}
im = c.indexes
} else {
c, _ = r.registerComposer(rv.Type(), nil)
c, _ = r.registerComposer(rt, nil, typeName)
im = c.indexes
}
for k := range im {
Expand All @@ -477,7 +497,7 @@ func (r *Recomposer) recomp(v any, rv reflect.Value) {
}
}
if has && m != nil {
r.setValue(m, f, &sf)
r.setValue(m, f, &sf, typeName)
}
}
case reflect.Interface:
Expand All @@ -497,7 +517,7 @@ func (r *Recomposer) recomp(v any, rv reflect.Value) {
}
}

func (r *Recomposer) setValue(v any, rv reflect.Value, sf *reflect.StructField) {
func (r *Recomposer) setValue(v any, rv reflect.Value, sf *reflect.StructField, parent string) {
switch rv.Kind() {
case reflect.Bool:
if s, ok := v.(string); ok && sf != nil && strings.Contains(sf.Tag.Get("json"), ",string") {
Expand Down Expand Up @@ -549,7 +569,7 @@ func (r *Recomposer) setValue(v any, rv reflect.Value, sf *reflect.StructField)
rv.Set(reflect.ValueOf(v))
case reflect.Ptr:
ev := reflect.New(rv.Type().Elem())
r.recomp(v, ev)
r.recomp(v, ev, "")
rv.Set(ev)
default:
if reflect.PtrTo(rv.Type()).Implements(jsonUnmarshalerType) {
Expand All @@ -562,6 +582,9 @@ func (r *Recomposer) setValue(v any, rv reflect.Value, sf *reflect.StructField)
return
}
}
r.recomp(v, rv)
if 0 < len(parent) {
parent += "." + sf.Name
}
r.recomp(v, rv, parent)
}
}
35 changes: 35 additions & 0 deletions alt/recomposer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,3 +856,38 @@ func TestRecomposeReflectArray(t *testing.T) {

tt.Panic(t, func() { _ = r.MustRecompose(map[string]any{"a": 1}, &tri) })
}

func TestRecomposeAnonymousEmbeddedStructs(t *testing.T) {
type AnonBed struct {
F1 int
F2 struct {
F21 int
F22 struct {
F221 int
F222 int
}
F23 int
}
F3 int
}
r := alt.MustNewRecomposer("^", nil)
src := map[string]any{
"f1": 1,
"f2": map[string]any{
"f21": 21,
"f22": map[string]any{
"f221": 221,
"f222": 222,
},
"f23": 23,
},
"f3": 3,
}
var ab AnonBed
_ = r.MustRecompose(src, &ab)
tt.Equal(t, `{
f1: 1
f2: {f21: 21 f22: {f221: 221 f222: 222} f23: 23}
f3: 3
}`, pretty.SEN(ab))
}
21 changes: 10 additions & 11 deletions notes
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@

- option for fast vs accurate parse
- doc should indicate 16th place variation and diff in performance for float parse

- jp [( scripts
- Get
+ First
- Locate
- Walk

- parse
- add discover option to find JSON or SEN in a string or file
Expand All @@ -19,10 +11,17 @@
- if number then cb and drop
- errors don't panic but close off current (if whole) and then back to discover maps
- implement on all parsers
- sen.Discover(b []byte, optimistic bool, f func(v any))
- fast scan to identify block
- look for { or [
- then count up and down matching on {} []
- detect start and end of " and ', skipping escaped
- if much faster then parse in separater channel
- handles embedded json
- maybe faster in some cases
- maybe a optimistic mode that assumes json/sen so count and minimal modes
- regular mode - attempt parse, if fails then try next

- embedded struct pointer encoding issue
- json.Unmarshaler
- use alt.Recompose and convert simple to bytes and pass to unmarshaller

- pretty
- align maps as well as arrays
Expand Down