diff --git a/accessors.go b/accessors.go index a8038d7..a510b2b 100644 --- a/accessors.go +++ b/accessors.go @@ -202,3 +202,58 @@ func interSlice(slice interface{}) ([]interface{}, bool) { return ret, true } + +// has checks whether the specified selector exists within the object. +func has(current interface{}, selector string) bool { + thisSel, nextSel := getKey(selector) + + indexes := []int{} + for strings.Contains(thisSel, "[") { + prevSel := thisSel + index := -1 + index, thisSel = getIndex(thisSel) + indexes = append(indexes, index) + if prevSel == thisSel { + break + } + } + + if curMap, ok := current.(Map); ok { + current = map[string]interface{}(curMap) + } + + switch cur := current.(type) { + case map[string]interface{}: + val, ok := cur[thisSel] + if !ok { + return false + } + current = val + default: + return false + } + + if len(indexes) > 0 { + num := len(indexes) + for num > 0 { + num-- + index := indexes[num] + indexes = indexes[:num] + if array, ok := interSlice(current); ok { + if index >= 0 && index < len(array) { + current = array[index] + } else { + return false + } + } else { + return false + } + } + } + + if nextSel != "" { + return has(current, nextSel) + } + + return true +} diff --git a/tests.go b/tests.go index d9e0b47..4f73da2 100644 --- a/tests.go +++ b/tests.go @@ -8,7 +8,7 @@ func (m Map) Has(selector string) bool { if m == nil { return false } - return !m.Get(selector).IsNil() + return has(m, selector) } // IsNil gets whether the data is nil or not. diff --git a/tests_test.go b/tests_test.go index f5579b0..339d32d 100644 --- a/tests_test.go +++ b/tests_test.go @@ -22,3 +22,46 @@ func TestHas(t *testing.T) { assert.False(t, m.Has("nothing")) } + +func TestHasWithNil(t *testing.T) { + m := objx.Map{ + "nilField": nil, + "nested": objx.Map{ + "nilChild": nil, + "valid": "hello", + }, + "nilSlice": []interface{}{nil, "value"}, + } + + assert.True(t, m.Has("nilField")) + assert.True(t, m.Has("nested.nilChild")) + assert.True(t, m.Has("nested.valid")) + assert.True(t, m.Has("nilSlice[0]")) + assert.True(t, m.Has("nilSlice[1]")) + + assert.False(t, m.Has("nonExistent")) + assert.False(t, m.Has("nested.nonExistent")) + assert.False(t, m.Has("nilSlice[2]")) + assert.False(t, m.Has("nilField.child")) + assert.False(t, m.Has("nilSlice[0].child")) + + // Verify we can differentiate nil value from not found + assert.True(t, m.Has("nilField")) + assert.True(t, m.Get("nilField").IsNil()) + + assert.False(t, m.Has("nonExistent")) + assert.True(t, m.Get("nonExistent").IsNil()) +} + +func TestHasDifferentiateNilFromNotFound(t *testing.T) { + obj := objx.Map(map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": 5, + "baz": nil, + }, + }) + + assert.True(t, obj.Has("foo.bar")) + assert.True(t, obj.Has("foo.baz")) + assert.False(t, obj.Has("foo.qux")) +}