Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions decode_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,24 @@
}
}

func LegacyStringToSliceHookFunc(sep string) DecodeHookFunc {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrzysztofKornalewski-Reply Is this sufficient or do we also need to handle lists as well? I think the example in #64 is "" to []int{}.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this should be sufficient, as you know from this migration PR:
openbao/openbao#290
There are two errors, and based on my tests and debugging, this approach resolves both. When I manually added LegacyStringToSliceHookFunc, both failed test cases were resolved.

The conversion problem for "1,2,3," for example, occurs because the new Hook implementation of StringToSliceHookFunc() only works when the target is a slice of strings. Previously, it worked for any slices.

About "" to []int{} it also occurs because of workflow has changed in StringToSliceHookFunc().

They also store this solution locally in spf13/viper.
https://github.com/spf13/viper/blob/260b8e9a3c75f49c374251f10fa1529d9e704b6a/viper.go#L1000

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrzysztofKornalewski-Reply Ahh, I see now. Makes sense, thank you!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrzysztofKornalewski-Reply In the meantime, you could also open this as a PR against OpenBao I think, and see if that solves the issues with PR 290 :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a comment why this is added?

I have added this to Viper: https://github.com/spf13/viper/blob/master/viper.go#L1000

I also wonder if it would make sense to call it StringToWeakSlice instead (as in Viper).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to migrate from the old mapstructure to the go-viper/mapstruct without additional dependencies. I'm wondering if the official approach requires importing the spy13/viper repository to use the handler you mentioned above.

If yes, then okay.

Copy link
Author

@KrzysztofKornalewski-Reply KrzysztofKornalewski-Reply Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But spy13/viper require some additional changes, we don't use Viper structure.
func (v *Viper) defaultDecoderConfig

We would like to use it as a normal function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrzysztofKornalewski-Reply that's just an example. It's basically the same function that you submitted.

I copied it earlier to Viper to keep backward compatibility.

I linked it as an example to demonstrate:

  • what kind of comment I asked for
  • what I called the function there

Legacy feels outdated, fragile.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sagikazarmark Is there any chance to keep this legacy hook in a repository?

return func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {

Check failure on line 168 in decode_hooks.go

View workflow job for this annotation

GitHub Actions / Lint

File is not properly formatted (gofumpt)
if f != reflect.String || t != reflect.Slice {
return data, nil
}

raw := data.(string)
if raw == "" {
return []string{}, nil
}

return strings.Split(raw, sep), nil
}
}

// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
// strings to time.Duration.
func StringToTimeDurationHookFunc() DecodeHookFunc {
Expand Down
40 changes: 40 additions & 0 deletions decode_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,46 @@ func TestStringToSliceHookFunc(t *testing.T) {
}
}

func TestLegacyStringToSliceHookFunc(t *testing.T) {
f := LegacyStringToSliceHookFunc(",")

strValue := reflect.ValueOf("42")
sliceValue := reflect.ValueOf([]string{"42"})
cases := []struct {
f, t reflect.Value
result interface{}
err bool
}{
{sliceValue, sliceValue, []string{"42"}, false},
{reflect.ValueOf([]byte("42")), reflect.ValueOf([]byte{}), []byte("42"), false},
{strValue, strValue, "42", false},
{
reflect.ValueOf("foo,bar,baz"),
sliceValue,
[]string{"foo", "bar", "baz"},
false,
},
{
reflect.ValueOf(""),
sliceValue,
[]string{},
false,
},
}

for i, tc := range cases {
actual, err := DecodeHookExec(f, tc.f, tc.t)
if tc.err != (err != nil) {
t.Fatalf("case %d: expected err %#v", i, tc.err)
}
if !reflect.DeepEqual(actual, tc.result) {
t.Fatalf(
"case %d: expected %#v, got %#v",
i, tc.result, actual)
}
}
}

func TestStringToTimeDurationHookFunc(t *testing.T) {
f := StringToTimeDurationHookFunc()

Expand Down
Loading