如何将 JSON 数组作为参数传递给 Cobra CLI?

huangapple go评论76阅读模式
英文:

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"

huangapple
  • 本文由 发表于 2021年12月9日 14:03:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/70285369.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定