From 429bfefa56f6a5ebd6279c32a7ced96cb5700ae7 Mon Sep 17 00:00:00 2001 From: Yusuf Efe <120668197+yuefdev@users.noreply.github.com> Date: Sun, 18 Oct 2026 23:17:01 +0300 Subject: [PATCH 1/3] Fix Set on nil nested maps Signed-off-by: Yusuf Efe <120668197+yuefdev@users.noreply.github.com> --- accessors.go | 2 +- accessors_test.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/accessors.go b/accessors.go index ab958e2..c86f8f4 100644 --- a/accessors.go +++ b/accessors.go @@ -235,7 +235,7 @@ func access(current any, selector string, value any, action accessAction) (any, _, ok = curMSI[thisSel].(Map) } - if (curMSI[thisSel] == nil || !ok) && len(indexes) == 0 && action == actionSet { + if len(indexes) == 0 && action == actionSet && (!ok || reflect.ValueOf(curMSI[thisSel]).IsNil()) { // An overflowing bracket index is parsed as a map key. Do not // replace an existing slice with a map to follow it. if reflect.ValueOf(curMSI[thisSel]).Kind() == reflect.Slice && overflow.overflows(nextSel) { diff --git a/accessors_test.go b/accessors_test.go index f0d51fd..d770675 100644 --- a/accessors_test.go +++ b/accessors_test.go @@ -154,6 +154,20 @@ func TestAccessorsAccessSetDeep(t *testing.T) { assert.Equal(t, "Ryer", m.Get("name.last").Data()) } +func TestAccessorsAccessSetNilNestedMap(t *testing.T) { + for name, nested := range map[string]any{ + "nil": nil, + "map": map[string]any(nil), + "objxMap": objx.Map(nil), + } { + t.Run(name, func(t *testing.T) { + m := objx.Map{"nested": nested} + m.Set("nested.answer", 42) + assert.Equal(t, 42, m.Get("nested.answer").Data()) + }) + } +} + func TestAccessorsAccessSetDeepDeep(t *testing.T) { m := objx.Map{ "one": objx.Map{ From 9b3fd9490b59a5127b2eeb5904ac9fbd16e55ad5 Mon Sep 17 00:00:00 2001 From: Yusuf Efe <120668197+yuefdev@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:24:19 +0300 Subject: [PATCH 2/3] fix: skip nil maps reached through slice indexes --- accessors.go | 3 +++ accessors_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/accessors.go b/accessors.go index c86f8f4..7361bc6 100644 --- a/accessors.go +++ b/accessors.go @@ -213,6 +213,9 @@ func access(current any, selector string, value any, action accessAction) (any, switch current.(type) { case map[string]any: curMSI := current.(map[string]any) + if curMSI == nil && action == actionSet { + return nil, false + } if nextSel == "" { switch { case action == actionSet && len(indexes) == 0: diff --git a/accessors_test.go b/accessors_test.go index d770675..2f32027 100644 --- a/accessors_test.go +++ b/accessors_test.go @@ -164,10 +164,33 @@ func TestAccessorsAccessSetNilNestedMap(t *testing.T) { m := objx.Map{"nested": nested} m.Set("nested.answer", 42) assert.Equal(t, 42, m.Get("nested.answer").Data()) + assert.Equal(t, map[string]any{"answer": 42}, m["nested"]) }) } } +func TestAccessorsAccessSetNilMapInSlice(t *testing.T) { + for name, items := range map[string]any{ + "nil": []any{nil}, + "map": []any{map[string]any(nil)}, + "objxMap": []any{objx.Map(nil)}, + "typed map slice": []map[string]any{nil}, + "typed objx slice": []objx.Map{nil}, + } { + t.Run(name, func(t *testing.T) { + m := objx.Map{"items": items} + m.Set("items[0].answer", 42) + assert.Nil(t, m.Get("items[0]").Data()) + }) + } +} + +func TestAccessorsAccessSetNilMap(t *testing.T) { + var m objx.Map + m.Set("answer", 42) + assert.Nil(t, m) +} + func TestAccessorsAccessSetDeepDeep(t *testing.T) { m := objx.Map{ "one": objx.Map{ From 2e6e22d8dd27821c971489ba7c9bd1a1f231adc4 Mon Sep 17 00:00:00 2001 From: Yusuf Efe <120668197+yuefdev@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:05:07 +0300 Subject: [PATCH 3/3] fix: preserve Set panic on a nil root map --- accessors.go | 8 +++++--- accessors_test.go | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/accessors.go b/accessors.go index 7361bc6..7636170 100644 --- a/accessors.go +++ b/accessors.go @@ -213,9 +213,6 @@ func access(current any, selector string, value any, action accessAction) (any, switch current.(type) { case map[string]any: curMSI := current.(map[string]any) - if curMSI == nil && action == actionSet { - return nil, false - } if nextSel == "" { switch { case action == actionSet && len(indexes) == 0: @@ -275,6 +272,11 @@ func access(current any, selector string, value any, action accessAction) (any, return nil, false } } + if action == actionSet { + if v := reflect.ValueOf(current); v.Kind() == reflect.Map && v.IsNil() { + return nil, false + } + } } if nextSel != "" { diff --git a/accessors_test.go b/accessors_test.go index 2f32027..5d53ad6 100644 --- a/accessors_test.go +++ b/accessors_test.go @@ -187,8 +187,7 @@ func TestAccessorsAccessSetNilMapInSlice(t *testing.T) { func TestAccessorsAccessSetNilMap(t *testing.T) { var m objx.Map - m.Set("answer", 42) - assert.Nil(t, m) + assert.Panics(t, func() { m.Set("answer", 42) }) } func TestAccessorsAccessSetDeepDeep(t *testing.T) {