diff --git a/README.md b/README.md
index 1b34748..06bc1fe 100644
--- a/README.md
+++ b/README.md
@@ -129,6 +129,7 @@ func main() {
Method and description |
Returns |
+
AsEmpty()
@@ -136,6 +137,7 @@ func main() {
|
*Optional[T] |
+
Clear()
@@ -144,6 +146,15 @@ func main() {
|
*Optional[T] |
+
+
+
+ Default(v T)
+ returns the value if present, otherwise returns the provided default value
+ |
+ T |
+
+
Filter(f func(v T) bool)
@@ -152,6 +163,7 @@ func main() {
|
*Optional[T] |
+
Get()
@@ -159,6 +171,7 @@ func main() {
|
(T, error) |
+
GetOk()
@@ -167,6 +180,7 @@ func main() {
|
(T, bool) |
+
IfElse(condition bool, other T)
@@ -175,6 +189,7 @@ func main() {
|
T |
+
IfPresent(f func(v T))
@@ -183,6 +198,7 @@ func main() {
|
*Optional[T] |
+
IfPresentOtherwise(f func(v T), other func())
@@ -191,6 +207,7 @@ func main() {
|
*Optional[T]
|
+
IfSet(f func(v T), notPresent func())
@@ -201,6 +218,7 @@ func main() {
|
*Optional[T] |
+
IfSetOtherwise(f func(v T), notPresent func(), other func())
@@ -211,6 +229,7 @@ func main() {
|
*Optional[T]
|
+
IsPresent()
@@ -218,6 +237,7 @@ func main() {
|
bool |
+
Map(f func(v T) any)
@@ -227,6 +247,7 @@ func main() {
|
*Optional[any] |
+
MarshalJSON()
@@ -236,6 +257,7 @@ func main() {
|
([]byte, error) |
+
OrElse(other T)
@@ -243,6 +265,7 @@ func main() {
|
T |
+
OrElseError(err error)
@@ -250,6 +273,7 @@ func main() {
|
error |
+
OrElseGet(f func() T)
@@ -257,6 +281,7 @@ func main() {
|
T |
+
OrElsePanic(v any)
@@ -265,6 +290,7 @@ func main() {
|
*Optional[T] |
+
OrElseSet(v T)
@@ -272,6 +298,7 @@ func main() {
|
*Optional[T] |
+
Scan(value interface{})
@@ -279,6 +306,7 @@ func main() {
|
error |
+
UnSet()
@@ -287,6 +315,7 @@ func main() {
|
*Optional[T] |
+
UnmarshalJSON(data []byte)
@@ -297,6 +326,7 @@ func main() {
|
error |
+
WasSet()
@@ -306,6 +336,7 @@ func main() {
|
bool |
+
WasSetElse(other T)
@@ -313,6 +344,7 @@ func main() {
|
T |
+
WasSetElseError(err error)
@@ -321,6 +353,7 @@ func main() {
|
error |
+
WasSetElseGet(f func() T)
@@ -329,6 +362,7 @@ func main() {
|
T |
+
WasSetElsePanic(v any)
@@ -337,6 +371,7 @@ func main() {
|
*Optional[T] |
+
WasSetElseSet(v T)
@@ -398,7 +433,7 @@ func main() {
|
EmptyInt8() *Optional[int8]
-7 returns an empty optional of type int8
+ returns an empty optional of type int8
|
diff --git a/maps.go b/maps.go
index 601edb1..eb44475 100644
--- a/maps.go
+++ b/maps.go
@@ -1,32 +1,94 @@
package gopt
+// Extract extracts an optional value, of the specified type, from a map
+//
+// If the key is present (and the value is non-nil and of the specified type) then an optional with the value is returned, otherwise an empty optional is returned
+func Extract[K comparable, T any](m map[K]any, key K) *Optional[T] {
+ result := Empty[T]()
+ if rv, ok := m[key]; ok && isPresent(rv) {
+ if v, ok := rv.(T); ok {
+ result = Of[T](v)
+ }
+ }
+ return result
+}
+
+// ExtractJson extracts an optional value, of the specified type, from a map[string]interface{}
+//
+// If the key is present (and the value is non-nil and of the specified type) then an optional with the value is returned, otherwise an empty optional is returned
+func ExtractJson[T any](m map[string]interface{}, key string) *Optional[T] {
+ result := Empty[T]()
+ if rv, ok := m[key]; ok && isPresent(rv) {
+ if v, ok := rv.(T); ok {
+ result = Of[T](v)
+ }
+ }
+ return result
+}
+
+// Get obtains an optional from a map
+//
+// If the key is present (and the value is non-nil) then an optional with the value is returned, otherwise an empty optional is returned
+func Get[K comparable, T any](m map[K]T, key K) *Optional[T] {
+ if v, ok := m[key]; ok && isPresent(v) {
+ return Of(v)
+ }
+ return Empty[T]()
+}
+
// OptMap can be used to cast an existing map for optional/functional methods
type OptMap[K comparable, V any] map[K]V
// Get returns an optional of the value in the map
//
// If the value is not present (or is nil) then an empty optional is returned
-func (m OptMap[K, V]) Get(k K) *Optional[V] {
- return Map(m, k)
+func (m OptMap[K, V]) Get(key K) *Optional[V] {
+ return Get(m, key)
}
// Default returns the value (if present and non-nil) otherwise returns the default value
-func (m OptMap[K, V]) Default(k K, def V) V {
- return Map(m, k).Default(def)
+func (m OptMap[K, V]) Default(key K, def V) V {
+ return Get(m, key).Default(def)
+}
+
+// IfPresent if the key is present (and the value is non-nil) calls the supplied function with the key and value
+//
+// otherwise does nothing
+func (m OptMap[K, V]) IfPresent(key K, f func(key K, v V)) OptMap[K, V] {
+ if f != nil {
+ if v, ok := m[key]; ok && isPresent(v) {
+ f(key, v)
+ }
+ }
+ return m
+}
+
+// IfPresentOtherwise if the key is present (and the value is non-nil) calls the supplied function with the key and value
+//
+// otherwise calls the other function with they key
+func (m OptMap[K, V]) IfPresentOtherwise(key K, f func(key K, v V), other func(key K)) OptMap[K, V] {
+ if v, ok := m[key]; ok && isPresent(v) {
+ if f != nil {
+ f(key, v)
+ }
+ } else if other != nil {
+ other(key)
+ }
+ return m
}
// ComputeIfAbsent if the specified key is not present (or the value is nil) sets the value according to the specified function
//
// returns either the existing value or the newly set value
-func (m OptMap[K, V]) ComputeIfAbsent(k K, f func(k K) V) V {
- if v, ok := m[k]; ok && isPresent(v) {
+func (m OptMap[K, V]) ComputeIfAbsent(key K, f func(key K) V) V {
+ if v, ok := m[key]; ok && isPresent(v) {
return v
}
var rv V
if f != nil {
- rv = f(k)
+ rv = f(key)
if isPresent(rv) {
- m[k] = rv
+ m[key] = rv
}
}
return rv
@@ -35,14 +97,14 @@ func (m OptMap[K, V]) ComputeIfAbsent(k K, f func(k K) V) V {
// ComputeIfPresent if the specified key is present (and the value is non-nil) attempts to compute a new mapping using the supplied function
//
// If the supplied function is called but returns a nil value, the key is deleted
-func (m OptMap[K, V]) ComputeIfPresent(k K, f func(k K, v V) V) V {
+func (m OptMap[K, V]) ComputeIfPresent(key K, f func(key K, v V) V) V {
var rv V
- if v, ok := m[k]; ok && isPresent(v) && f != nil {
- rv = f(k, v)
+ if v, ok := m[key]; ok && isPresent(v) && f != nil {
+ rv = f(key, v)
if isPresent(rv) {
- m[k] = rv
+ m[key] = rv
} else {
- delete(m, k)
+ delete(m, key)
}
}
return rv
@@ -51,9 +113,9 @@ func (m OptMap[K, V]) ComputeIfPresent(k K, f func(k K, v V) V) V {
// PutIfAbsent if the specified key if absent (not present or nil-value) it is set to the specified value
//
// returns true if the value was set
-func (m OptMap[K, V]) PutIfAbsent(k K, v V) bool {
- if ov, ok := m[k]; !ok || !isPresent(ov) {
- m[k] = v
+func (m OptMap[K, V]) PutIfAbsent(key K, v V) bool {
+ if ov, ok := m[key]; !ok || !isPresent(ov) {
+ m[key] = v
return true
}
return false
@@ -64,14 +126,14 @@ func (m OptMap[K, V]) PutIfAbsent(k K, v V) bool {
// returns true if the value was replaced
//
// If the specified replacement value is nil and
-func (m OptMap[K, V]) ReplaceIfPresent(k K, v V) bool {
+func (m OptMap[K, V]) ReplaceIfPresent(key K, v V) bool {
if isPresent(v) {
- if ov, ok := m[k]; ok && isPresent(ov) {
- m[k] = v
+ if ov, ok := m[key]; ok && isPresent(ov) {
+ m[key] = v
return true
}
- } else if _, ok := m[k]; ok {
- delete(m, k)
+ } else if _, ok := m[key]; ok {
+ delete(m, key)
return true
}
return false
diff --git a/maps_test.go b/maps_test.go
index 2a20d31..6cfc4d1 100644
--- a/maps_test.go
+++ b/maps_test.go
@@ -3,8 +3,107 @@ package gopt
import (
"github.com/stretchr/testify/require"
"testing"
+ "time"
)
+func TestExtract(t *testing.T) {
+ m := map[string]interface{}{
+ "str": "Str",
+ "int": 16,
+ "time": time.Now(),
+ }
+
+ s := Extract[string, string](m, "str")
+ require.True(t, s.IsPresent())
+ require.Equal(t, "Str", s.OrElse(""))
+
+ s = Extract[string, string](m, "int")
+ require.False(t, s.IsPresent())
+
+ i := Extract[string, int](m, "str")
+ require.False(t, i.IsPresent())
+
+ i = Extract[string, int](m, "int")
+ require.True(t, i.IsPresent())
+ require.Equal(t, 16, i.OrElse(0))
+
+ dt := Extract[string, time.Time](m, "time")
+ require.True(t, dt.IsPresent())
+
+ dt = Extract[string, time.Time](m, "str")
+ require.False(t, dt.IsPresent())
+
+ m2 := map[int]interface{}{
+ 1: "Str",
+ 2: 16,
+ }
+ s = Extract[int, string](m2, 1)
+ require.True(t, s.IsPresent())
+ require.Equal(t, "Str", s.OrElse(""))
+ s = Extract[int, string](m2, 2)
+ require.False(t, s.IsPresent())
+ i = Extract[int, int](m2, 2)
+ require.True(t, i.IsPresent())
+ require.Equal(t, 16, i.OrElse(0))
+ i = Extract[int, int](m2, 1)
+ require.False(t, i.IsPresent())
+}
+
+func TestExtractJson(t *testing.T) {
+ m := map[string]interface{}{
+ "str": "Str",
+ "int": 16,
+ "time": time.Now(),
+ }
+
+ s := ExtractJson[string](m, "str")
+ require.True(t, s.IsPresent())
+ require.Equal(t, "Str", s.OrElse(""))
+
+ s = ExtractJson[string](m, "int")
+ require.False(t, s.IsPresent())
+
+ i := ExtractJson[int](m, "str")
+ require.False(t, i.IsPresent())
+
+ i = ExtractJson[int](m, "int")
+ require.True(t, i.IsPresent())
+ require.Equal(t, 16, i.OrElse(0))
+
+ dt := ExtractJson[time.Time](m, "time")
+ require.True(t, dt.IsPresent())
+
+ dt = ExtractJson[time.Time](m, "str")
+ require.False(t, dt.IsPresent())
+}
+
+func TestGet(t *testing.T) {
+ m := map[string]interface{}{
+ "foo": "foo value",
+ "bar": 1,
+ }
+ o := Get(m, "foo")
+ require.True(t, o.IsPresent())
+ require.Equal(t, "foo value", o.OrElse(nil))
+ o = Get(m, "bar")
+ require.True(t, o.IsPresent())
+ require.Equal(t, 1, o.OrElse(nil))
+ o = Get(m, "baz")
+ require.False(t, o.IsPresent())
+ require.Equal(t, "", o.OrElse(""))
+
+ m2 := map[int]*myStruct{
+ 1: {},
+ 2: nil,
+ }
+ o2 := Get(m2, 1)
+ require.True(t, o2.IsPresent())
+ o2 = Get(m2, 2)
+ require.False(t, o2.IsPresent())
+ o2 = Get(m2, 3)
+ require.False(t, o2.IsPresent())
+}
+
func TestOptMap_Get(t *testing.T) {
m := map[string]interface{}{
"foo": "foo value",
@@ -19,7 +118,64 @@ func TestOptMap_Get(t *testing.T) {
require.False(t, ov.IsPresent())
}
-func TestOptMap_GetOrDefault(t *testing.T) {
+func TestOptMap_IfPresent(t *testing.T) {
+ m := map[string]interface{}{
+ "foo": "foo value",
+ "bar": nil,
+ }
+ om := OptMap[string, interface{}](m)
+ called := false
+ f := func(key string, v interface{}) {
+ called = true
+ }
+ om.IfPresent("foo", f)
+ require.True(t, called)
+ called = false
+ om.IfPresent("bar", f)
+ require.False(t, called)
+ om.IfPresent("baz", f)
+ require.False(t, called)
+
+ om.IfPresent("bar", f).IfPresent("foo", f)
+ require.True(t, called)
+}
+
+func TestOptMap_IfPresentOtherwise(t *testing.T) {
+ m := map[string]interface{}{
+ "foo": "foo value",
+ "bar": nil,
+ }
+ om := OptMap[string, interface{}](m)
+ called := false
+ otherCalled := false
+ f := func(key string, v interface{}) {
+ called = true
+ }
+ other := func(key string) {
+ otherCalled = true
+ }
+ om.IfPresentOtherwise("foo", f, other)
+ require.True(t, called)
+ require.False(t, otherCalled)
+ called = false
+ otherCalled = false
+ om.IfPresentOtherwise("bar", f, other)
+ require.False(t, called)
+ require.True(t, otherCalled)
+ called = false
+ otherCalled = false
+ om.IfPresentOtherwise("baz", f, other)
+ require.False(t, called)
+ require.True(t, otherCalled)
+
+ called = false
+ otherCalled = false
+ om.IfPresentOtherwise("bar", f, other).IfPresentOtherwise("foo", f, other)
+ require.True(t, called)
+ require.True(t, otherCalled)
+}
+
+func TestOptMap_Default(t *testing.T) {
m := map[string]interface{}{
"foo": "foo value",
"bar": nil,
diff --git a/optional.go b/optional.go
index 93d1a27..c7a67d8 100644
--- a/optional.go
+++ b/optional.go
@@ -52,6 +52,30 @@ func Empty[T any]() *Optional[T] {
}
}
+// MapTo maps the supplied optional to the specified type (if present and can can be converted) and returns an optional of the mapped value
+func MapTo[T any](o *Optional[any]) *Optional[T] {
+ if o != nil && o.present {
+ if v, ok := o.value.(T); ok {
+ return Of[T](v)
+ }
+ }
+ return Empty[T]()
+}
+
+// ConvertTo converts the supplied optional to the specified type (if present and can can be converted) and returns an optional of converted value
+//
+// If the present value cannot be converted, the supplied conversion function is called
+func ConvertTo[F any, T any](o *Optional[F], f func(v F) *Optional[T]) *Optional[T] {
+ if o != nil && o.present {
+ if v, ok := any(o.value).(T); ok {
+ return Of[T](v)
+ } else if f != nil {
+ return f(o.value)
+ }
+ }
+ return Empty[T]()
+}
+
func isPresent(v any) bool {
vo := reflect.ValueOf(v)
switch vk := vo.Kind(); vk {
@@ -110,7 +134,7 @@ func (o *Optional[T]) Get() (T, error) {
return o.value, nil
}
-// Default returns the value if present, otherwise returns the defaulted value
+// Default returns the value if present, otherwise returns the provided default value
func (o *Optional[T]) Default(v T) T {
if !o.present {
return v
@@ -194,7 +218,7 @@ func (o *Optional[T]) IsPresent() bool {
return o.present
}
-// Map if the value is present and the result of calling the supplied mapping function returns non-nil, returns
+// Get if the value is present and the result of calling the supplied mapping function returns non-nil, returns
// an optional describing that returned value
//
// Otherwise returns an empty optional
@@ -290,18 +314,24 @@ func (o *Optional[T]) Scan(value interface{}) error {
return err
} else if bd, ok := value.([]byte); ok {
var uv T
- if unErr := json.Unmarshal(bd, &uv); unErr == nil {
- if isPresent(uv) {
- o.present = true
- o.value = uv
- } else {
- o.present = false
- o.value = o.emptyValue()
- }
+ if o.isString() {
+ o.present = true
o.set = true
+ o.value = any(string(bd)).(T)
} else {
- o.clear(true)
- return unErr
+ if unErr := json.Unmarshal(bd, &uv); unErr == nil {
+ if isPresent(uv) {
+ o.present = true
+ o.value = uv
+ } else {
+ o.present = false
+ o.value = o.emptyValue()
+ }
+ o.set = true
+ } else {
+ o.clear(true)
+ return unErr
+ }
}
} else {
o.clear(true)
@@ -415,6 +445,10 @@ func (o *Optional[T]) clear(set bool) {
o.set = set
}
+func (o *Optional[T]) isString() bool {
+ return reflect.TypeOf(o.value).Kind() == reflect.String
+}
+
func (o *Optional[T]) callScannable(value interface{}) (bool, error) {
var nv reflect.Value
if !isPresent(o.value) {
@@ -525,11 +559,3 @@ func EmptyByte() *Optional[byte] {
func EmptyRune() *Optional[rune] {
return Empty[rune]()
}
-
-// Map obtains an optional from a map - if the key is present (and the value is non-nil) then an optional with the value is returned, otherwise an empty optional is returned
-func Map[K comparable, T any](m map[K]T, key K) *Optional[T] {
- if v, ok := m[key]; ok && isPresent(v) {
- return Of(v)
- }
- return Empty[T]()
-}
diff --git a/optional_test.go b/optional_test.go
index 20e714f..edcf280 100644
--- a/optional_test.go
+++ b/optional_test.go
@@ -76,6 +76,38 @@ func TestOf(t *testing.T) {
require.False(t, opt3.IsPresent())
}
+func TestMapTo(t *testing.T) {
+ o1 := Of[interface{}]("foo")
+ o2 := MapTo[string](o1)
+ require.True(t, o2.IsPresent())
+ require.Equal(t, "foo", o2.OrElse(""))
+
+ o1 = Of[interface{}](1)
+ o2 = MapTo[string](o1)
+ require.False(t, o2.IsPresent())
+}
+
+func TestConvertTo(t *testing.T) {
+ o1 := Of[interface{}]("foo")
+ o2 := ConvertTo[interface{}, string](o1, nil)
+ require.True(t, o2.IsPresent())
+ require.Equal(t, "foo", o2.OrElse(""))
+
+ o1 = Of[interface{}](16)
+ o2 = ConvertTo[interface{}, string](o1, nil)
+ require.False(t, o2.IsPresent())
+ f := func(v interface{}) *Optional[string] {
+ switch vt := v.(type) {
+ case int:
+ return Of(strconv.Itoa(vt))
+ }
+ return EmptyString()
+ }
+ o2 = ConvertTo[interface{}, string](o1, f)
+ require.True(t, o2.IsPresent())
+ require.Equal(t, "16", o2.OrElse(""))
+}
+
func TestOptional_AsEmpty(t *testing.T) {
o := Of("abc")
require.True(t, o.IsPresent())
@@ -532,6 +564,15 @@ func TestOptional_Scan(t *testing.T) {
require.NoError(t, err)
require.False(t, o5.IsPresent())
require.True(t, o5.WasSet())
+
+ o6 := Empty[string]()
+ require.False(t, o6.IsPresent())
+ require.False(t, o6.WasSet())
+ err = o6.Scan([]uint8{'a', 'b', 'c'})
+ require.NoError(t, err)
+ require.True(t, o6.IsPresent())
+ require.True(t, o6.WasSet())
+ require.Equal(t, "abc", o6.OrElse(""))
}
func TestOptional_UnSet(t *testing.T) {
@@ -681,33 +722,6 @@ func TestOptional_Default(t *testing.T) {
require.Equal(t, "bar", v4.Foo)
}
-func TestMap(t *testing.T) {
- m := map[string]interface{}{
- "foo": "foo value",
- "bar": 1,
- }
- o := Map(m, "foo")
- require.True(t, o.IsPresent())
- require.Equal(t, "foo value", o.OrElse(nil))
- o = Map(m, "bar")
- require.True(t, o.IsPresent())
- require.Equal(t, 1, o.OrElse(nil))
- o = Map(m, "baz")
- require.False(t, o.IsPresent())
- require.Equal(t, "", o.OrElse(""))
-
- m2 := map[int]*myStruct{
- 1: {},
- 2: nil,
- }
- o2 := Map(m2, 1)
- require.True(t, o2.IsPresent())
- o2 = Map(m2, 2)
- require.False(t, o2.IsPresent())
- o2 = Map(m2, 3)
- require.False(t, o2.IsPresent())
-}
-
type scannable struct {
called bool
err error