diff --git a/accessors.go b/accessors.go index ab958e2..7636170 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) { @@ -272,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 f0d51fd..5d53ad6 100644 --- a/accessors_test.go +++ b/accessors_test.go @@ -154,6 +154,42 @@ 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()) + 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 + assert.Panics(t, func() { m.Set("answer", 42) }) +} + func TestAccessorsAccessSetDeepDeep(t *testing.T) { m := objx.Map{ "one": objx.Map{