英文:
How can I provide JSON array as argument to cobra cli
问题
我正在使用Go和Cobra库构建一个CLI。我有以下需要反序列化到相应结构体的JSON数据。
作为JSON数组的参数:
"[
(stringA, stringB),
stringC
]"
结构体:
type MyStruct struct {
StringArray []string
}
我正在使用Cobra的StringSliceVarP
方法,如下所示:
cmd.PersistentFlags().StringSliceVarP(&opts.StringParam, "paramname", "", nil, `this is the description`)
但是Cobra将传入的JSON读取为一个单独的字符串[(stringA, stringB), stringC]
,而我希望数组的长度为2,即StringArray[0]: (stringA, stringB)
和StringArray[1]:stringC
。
我不能使用StringSliceVarP
,因为它会根据逗号进行分割,而我不希望我的数组字符串本身包含逗号。
我该如何实现这个目标?
英文:
I'm building a CLI using Go and Cobra library. I've the following JSON that needs to be deserialized in the corresponding struct.
Argument as JSON array:
"[
(stringA, stringB),
stringC
]"
Struct
type MyStruct struct {
StringArray []string
}
I'm using Cobra's StringSicceVarP
as follows to
cmd.PersistentFlags().StringSliceVarP(&opts.StringParam, "paramname", "", nil, `this is the description`)
But cobra is reading the incoming json as one single string [(stringA, stringB), stringC]
whereas I want the array to be of length 2, such that
StringArray[0]: (stringA, stringB)
and StringArray[1]:stringC
.
I can't use the StringSliceVarP
as it will split based on ,
which I don't want as my array string could itself has a ,
.
How can I achieve this?
答案1
得分: 2
我个人建议你不要选择这个选项。通常,通过读取标准输入或从文件中读取格式化数据是常规做法。这种解决方案通常更加灵活,可以通过添加标志来指定文件的格式(JSON、XML等)。
在参数中提供文件名而不是原始的JSON字符串可以更好地与其他软件进行互操作,并带来其他好处,例如使用计算机的磁盘来缓冲数据,而不是使用计算机的内存/RAM。
我的个人建议是:
- 使用标志来设置选项和配置,类似于HTTP的查询参数。
- 使用标准输入/文件句柄来传递数据,类似于HTTP的请求体。
然而,如果你坚持使用标志:
Cobra没有内置对JSON结构的支持。然而,pflag包(Cobra使用的标志库)允许你通过pflag.(*FlagSet).Var()
方法定义自定义值类型作为标志。你需要创建一个实现pflag.Value
接口的新类型:
type Value interface {
String() string
Set(string) error
Type() string
}
为了创建一个自定义的JSON解析类型,你可以使用内置的encoding/json
包编写以下代码:
import (
"encoding/json"
)
type JSONFlag struct {
Target interface{}
}
// String用于fmt.Print和Cobra的帮助文本
func (f *JSONFlag) String() string {
b, err := json.Marshal(f.Target)
if err != nil {
return "无法序列化对象"
}
return string(b)
}
// Set必须使用指针接收器,以免改变副本的值
func (f *JSONFlag) Set(v string) error {
return json.Unmarshal([]byte(v), f.Target)
}
// Type仅在帮助文本中使用
func (f *JSONFlag) Type() string {
return "json"
}
然后,为了使用这个新的pflag.Value
兼容类型,你可以编写类似以下的代码:
import (
"fmt"
"github.com/spf13/cobra"
)
type MyStruct struct {
StringArray []string
}
func init() {
var flagMyStringArray []string
var myCmd = &cobra.Command{
Use: "mycmd",
Short: "你的命令的简要描述",
Run: func(cmd *cobra.Command, args []string) {
myStruct := MyStruct{StringArray: flagMyStringArray}
fmt.Printf("myStruct.StringArray 包含 %d 个元素:\n", len(myStruct.StringArray))
for i, s := range myStruct.StringArray {
fmt.Printf("idx=%d: %q", i, s)
}
},
}
rootCmd.AddCommand(myCmd)
myCmd.Flags().Var(&JSONFlag{&flagMyStringArray}, "paramname", `这是描述`)
}
示例用法:
$ go run . mycmd --paramname 'hello'
错误: 对于"--paramname"标志,无效的参数"hello":寻找值的开始时无效字符'h'
用法:
test mycmd [flags]
标志:
-h, --help mycmd的帮助
--paramname json 这是描述
退出状态 1
$ go run . mycmd --paramname '[(stringA, stringB), "stringC"]'
myStruct.StringArray 包含 2 个元素:
idx=0: "(stringA, stringB)"
idx=1: "stringC"
英文:
I personally advice you against this option. Supplying formatted data is conventionally done through reading STDIN or from a file. Such solution is usually more flexible by allowing you to add flags to specify the file's format (JSON, XML, etc.).
Supplying a filename instead of the raw JSON string in the arguments adds better interoperability with other software, and other benefits such as using the computer's disk for buffering data instead of the computer's memory/RAM.
My personal recommendations is that:
- Use flags for options and configs, similar to a HTTP's query parameters.
- Use stdin/file handles for data, similar to a HTTP's request body.
However, if you insist on using a flag:
Cobra does not have built-in support for JSON structures. However, the pflag package (the flag library used by Cobra) allows you to define custom value types to be used as flags through the pflag.(*FlagSet).Var()
method. You have to make a new type that implements the pflag.Value
interface:
type Value interface {
String() string
Set(string) error
Type() string
}
To make a custom JSON-parsing type, you could write the following to use the built-in encoding/json
package:
import (
"encoding/json"
)
type JSONFlag struct {
Target interface{}
}
// String is used both by fmt.Print and by Cobra in help text
func (f *JSONFlag) String() string {
b, err := json.Marshal(f.Target)
if err != nil {
return "failed to marshal object"
}
return string(b)
}
// Set must have pointer receiver so it doesn't change the value of a copy
func (f *JSONFlag) Set(v string) error {
return json.Unmarshal([]byte(v), f.Target)
}
// Type is only used in help text
func (f *JSONFlag) Type() string {
return "json"
}
Then to use this new pflag.Value
-compatible type, you may write something like this:
import (
"fmt"
"github.com/spf13/cobra"
)
type MyStruct struct {
StringArray []string
}
func init() {
var flagMyStringArray []string
var myCmd = &cobra.Command{
Use: "mycmd",
Short: "A brief description of your command",
Run: func(cmd *cobra.Command, args []string) {
myStruct := MyStruct{StringArray: flagMyStringArray}
fmt.Printf("myStruct.StringArray contains %d elements:\n", len(myStruct.StringArray))
for i, s := range myStruct.StringArray {
fmt.Printf("idx=%d: %q", i, s)
}
},
}
rootCmd.AddCommand(myCmd)
myCmd.Flags().Var(&JSONFlag{&flagMyStringArray}, "paramname", `this is the description`)
}
Example usage:
$ go run . mycmd --paramname 'hello'
Error: invalid argument "hello" for "--paramname" flag: invalid character 'h' looking for beginning of value
Usage:
test mycmd [flags]
Flags:
-h, --help help for mycmd
--paramname json this is the description
exit status 1
$ go run . mycmd --paramname '["(stringA, stringB)", "stringC"]'
myStruct.StringArray contains 2 elements:
idx=0: "(stringA, stringB)"
idx=1: "stringC"
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论