2018-07-09 13:11
浏览 311


I noticed that this is perhaps a bug when viper tries to unmarshall to struct. To explain it better, consider this:

I have a cli command like below dd-cli submit-bug --name "Bug 1" --tag reason1 --tag reason2

Here is my command line source code

package cmd

import (



// SubmitBugOpts is a set of flags being exposed by this Deploy command
type SubmitBugOpts struct {
    Name string `mapstructure:"bug-name"`

    ReasonTags []string `mapstructure:"tags"`

var (
    submitBugOpts = SubmitBugOpts{}

func submitBugRun(cmd *cobra.Command, args []string) {
    fmt.Printf("Bug Name is %+v
", submitBugOpts.Name)
    fmt.Printf("List of tags is %+v
", submitBugOpts.ReasonTags)
    fmt.Printf("Length of tags is %d
", len(submitBugOpts.ReasonTags))
    for index, el := range submitBugOpts.ReasonTags {
        fmt.Printf("tag[%d] = %s
", index, el)

var submitBugCmd = &cobra.Command{
    Use:   "submit-bug",
    Short: "Deploy/Install a helm chart to Kubernetes cluster",
    Run:   submitBugRun,
    PreRun: func(cmd *cobra.Command, args []string) {
        pFlags := cmd.PersistentFlags()
        viper.BindPFlag("bug-name", pFlags.Lookup("name"))
        viper.BindPFlag("tags", pFlags.Lookup("tag"))

        fmt.Printf("Viper all setting value: %+v
", viper.AllSettings())
        fmt.Printf("Before unmarshall: %+v
", submitBugOpts)
        fmt.Printf("After unmarshall: %+v
", submitBugOpts)

func init() {

    pFlags := submitBugCmd.PersistentFlags()
    pFlags.StringVar(&submitBugOpts.Name, "name", "", "the bug name")
    pFlags.StringArrayVar(&submitBugOpts.ReasonTags, "tag", nil, "the bug's reason tag. You can define it multiple times")


I run this command:

dd-cli submit-bug --name "Bug 1" --tag reason1 --tag reason2

And the output is below

Viper all setting value: map[bug-name:Bug 1 tags:[reason1,reason2]]
Before unmarshall: {Name:Bug 1 ReasonTags:[reason1 reason2]}
After unmarshall: {Name:Bug 1 ReasonTags:[[reason1 reason2]]}
Bug Name is Bug 1
List of tags is [[reason1 reason2]]
Length of tags is 2
tag[0] = [reason1
tag[1] = reason2]

I expect the viper.Unmarshall() will correctly omit the [ for submitBugOpts.ReasonTags [0] and omit the ] for submitBugOpts.ReasonTags[1]. So the expected value of submitBugOpts.ReasonTags doesn't contains any [ and ] .

Any pointer how to fix this? I've submitted this issue on viper repo: https://github.com/spf13/viper/issues/527. However I am asking on SO just in case you guys know how to handle this too.

  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • dqqpf32897 2018-07-13 15:11

    After digging into codes of github.com/spf13/{cobra,viper,pflag}for quite a while, I finally find the problem.

    When you call pFlags.StringArrayVar(&submitBugOpts.ReasonTags, "tag", nil, ...), the ReasonTags, of course, binds to a wrapper of pflag.stringArrayValue. source.

    And when you call viper.Unmarshall, viper uses v.Get to get the value binds to ReasonTags and then it calls v.find.

    In v.find, after the value is found, it uses the wrapper's ValueType() method to determine its type, the wrapper then calls the Type method of the wrapped type, pflag.stringArrayValue, and returns "stringArray". source

    But viper only handle "stringSlice" as a special case, so the value gets to default part of type switch, which uses its ValueString() method - making it into a string, with "[" and "]" on both side. source

    And when finally unmarshalling, as your output param, ReasonTags is of []string, the program simply splitted the string and set it into the field.

    As for solution, if you are ok with prohibitting tag to contain ,, simply change StringArrayVar to StringSliceVar, but this will results in --tag "Yet, the problem re-occurs" into []string{"Yet"," the problem re-occrus"}.

    If that is critical, you'll need to ask the developers of viper to create a case for stringArray.

    打赏 评论

相关推荐 更多相似问题