Skip to content

Commit

Permalink
add ForceJSON option for string decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
muir committed Mar 14, 2024
1 parent abaed17 commit f8bb8d7
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Expand Up @@ -18,7 +18,7 @@ jobs:

# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
# args: --out-format checkstyle
args: --out-format checkstyle

# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
Expand Down
28 changes: 26 additions & 2 deletions unpack.go
Expand Up @@ -2,6 +2,7 @@ package reflectutils

import (
"encoding"
"encoding/json"
"flag"
"reflect"
"strconv"
Expand All @@ -10,12 +11,15 @@ import (
"github.com/pkg/errors"
)

var textUnmarshallerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
var flagValueType = reflect.TypeOf((*flag.Value)(nil)).Elem()
var (
textUnmarshallerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
flagValueType = reflect.TypeOf((*flag.Value)(nil)).Elem()
)

type stringSetterOpts struct {
split string
sliceAppend bool
forceJSON bool
}

type StringSetterArg func(*stringSetterOpts)
Expand All @@ -41,6 +45,15 @@ func SliceAppend(b bool) StringSetterArg {
}
}

// ForceJSON controls if types will be decoded with JSON
// unmarshal. This overrides normal decoding patterns. The default
// is false.
func ForceJSON(b bool) StringSetterArg {
return func(o *stringSetterOpts) {
o.forceJSON = b
}
}

// MakeStringSetter handles setting a reflect.Value from a string.
// Based on type, it returns a function to do the work. It is assumed that the
// reflect.Type matches the reflect.Value. If not, panic is likely.
Expand All @@ -64,6 +77,17 @@ func MakeStringSetter(t reflect.Type, optArgs ...StringSetterArg) (func(target r
for _, f := range optArgs {
f(&opts)
}
if opts.forceJSON {
return func(target reflect.Value, value string) error {
p := reflect.New(t.Elem())
target.Set(p)
err := json.Unmarshal([]byte(value), target.Interface())
if err != nil {
return errors.WithStack(err)

Check warning on line 86 in unpack.go

View check run for this annotation

Codecov / codecov/patch

unpack.go#L86

Added line #L86 was not covered by tests
}
return nil
}, nil
}
if setter, ok := settersByType[t]; ok {
return func(target reflect.Value, value string) error {
out := setter.Call([]reflect.Value{reflect.ValueOf(value)})
Expand Down
14 changes: 13 additions & 1 deletion unpack_test.go
Expand Up @@ -30,13 +30,18 @@ func (bp *Bar) Set(s string) error {
*bp = Bar(s + "/e")
return nil
}

func (bp Bar) String() string {
return "b/" + string(bp)
}

var _ flag.Value = func() *Bar { var x Bar; return &x }()

func TestStringSetter(t *testing.T) {
type J struct {
A int
B string
}
type tsType struct {
Int int `value:"38"`
Int8 int8 `value:"-9"`
Expand Down Expand Up @@ -75,7 +80,7 @@ func TestStringSetter(t *testing.T) {
FooP *Foo `value:"foo" want:"~foo~"`
Dur time.Duration `value:"30m" want:"30m0s"`
DurP *time.Duration `value:"15m" want:"15m0s"`
DurArray []time.Duration `value:"15m,45m" want:"[15m0s 45m0s]"`
DurArray []time.Duration `value:"15m,45m" want:"[15m0s 45m0s]"`
Bar Bar `value:"bar" want:"b/bar/e"`
BarArray [2]Bar `value:"a,b,c" want:"[b/a/e b/b,c/e]"`
BarP *Bar `value:"bar" want:"b/bar/e"`
Expand All @@ -89,6 +94,7 @@ func TestStringSetter(t *testing.T) {
SS5 []string `value:"foo" want:"[foo bar]" value2:"bar"`
SS6 []string `value:"foo" want:"[bar]" value2:"bar" sa:"f"`
RG01 *[]int `value:"823:29" want:"[823 29]" split:":"`
S *J `value:"{\"A\":10,\"B\":\"bar\"}" want:"{A:10 B:bar}" fj:"t"`
}
var ts tsType
vp := reflect.ValueOf(&ts)
Expand Down Expand Up @@ -116,6 +122,12 @@ func TestStringSetter(t *testing.T) {
t.Log(" slice append", b)
opts = append(opts, reflectutils.SliceAppend(b))
}
if fj, ok := f.Tag.Lookup("fj"); ok {
b, err := strconv.ParseBool(fj)
require.NoError(t, err, "parse fj")
t.Log(" force JSON", b)
opts = append(opts, reflectutils.ForceJSON(b))
}

fn, err := reflectutils.MakeStringSetter(f.Type, opts...)
if !assert.NoErrorf(t, err, "make string setter for %s", f.Name) {
Expand Down

0 comments on commit f8bb8d7

Please sign in to comment.