如何从JSON数组中删除尾随逗号

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

How to remove trailing commas from array in JSON

问题

I can help with the translation:

json=$(printf '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$(printf '"%s",' "${imgArray[@]%,}")" "$(printf '"%s",' "${typeArray[@]}")")

这是格式化JSON的printf语句。在输出中,末尾的逗号是由%s,语句在printf中引起的。

要修改printf语句以不在数组的最后一项使用逗号,您可以使用sed等命令来处理JSON字符串以去除末尾逗号。

英文:

I am trying make a bash script to get user input, format that input into a JSON, then post that JSON to a Firebase Realtime Database using the REST API. After getting the input, I use printf statements to format the input into a JSON. The issue is that I have a trailing comma after the last entry in an array.

Here is the printf statement to format the JSON

json=$(printf '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$(printf '"%s",' "${imgArray[@]%,}")" "$(printf '"%s",' "${typeArray[@]}")")

Here is a sample output

{"itemName":"itemname","images":["url1","url2","url3",],"type":["all","hats",],"link":"/"}

I understand the training comma comes from the %s, statement in the printf.

Is there a way to modify the printf statement to not use a comma at the last entry in the array? Or is there a way to use commands like sed to remove that trailing comma?

答案1

得分: 1

Assumptions:

  • the 1st element in both arrays have index 0

Sample inputs:

itemName='itemname'
imgArray=(url1 url2 url3)
typeArray=(all hats)

NOTE: While there may be a way to do this with a json tool (eg, jq), I'll focus on some general bash capabilities ...

The general approach is to print the 1st element by itself, then preface the 2nd-nth elements with a comma.

Breaking the current printf calls into two parts:

# these:

$(printf '"%s",' "${imgArray[@]%,}")
$(printf '"%s",' "${typeArray[@]}")

# becomes these:

$(printf '"%s"' "${imgArray[0]}" ; printf ',"%s"' "${imgArray[@]:1}")
$(printf '"%s"' "${typeArray[0]}"; printf ',"%s"' "${typeArray[@]:1}")

This (still) requires OP's code to make 3 subprocess calls.

One idea to eliminate the subprocesses consists of using the (bash) printf builtin to populate some variables (imgs, types, json):

printf -v imgs ',"%s"' "${imgArray[@]:1}"            # preface 2nd-nth elements with a comma
imgs="'${imgArray[0]}'${imgs}"                     # add 1st element

printf -v types ',"%s"' "${typeArray[@]:1}"          # preface 2nd-nth elements with a comma
types="'${typeArray[0]}'${types}"                  # add 1st element

printf -v json '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$imgs" "$types"

This generates:

$ typeset -p json
declare -- json="{"itemName":"itemname","images":["url1","url2","url3"],"type":["all","hats"],"link":"/"}"

$ echo "$json"
{"itemName":"itemname","images":["url1","url2","url3"],"type":["all","hats"],"link":"/"}
英文:

Assumptions:

  • the 1st element in both arrays have index 0

Sample inputs:

itemName='itemname'
imgArray=(url1 url2 url3)
typeArray=(all hats)

NOTE: While there may be a way to do this with a json tool (eg, jq), I'll focus on some general bash capabilities ...

The general approach is to print the 1st element by itself, then preface the 2nd-nth elements with a comma.

Breaking the current printf calls into two parts:

# these:

$(printf '"%s",' "${imgArray[@]%,}")
$(printf '"%s",' "${typeArray[@]}")

# becomes these:

$(printf '"%s"' "${imgArray[0]}" ; printf ',"%s"' "${imgArray[@]:1}")
$(printf '"%s"' "${typeArray[0]}"; printf ',"%s"' "${typeArray[@]:1}")

This (still) requires OP's code to make 3 subprocess calls.

One idea to eliminate the subprocesses consists of using the (bash) printf builtin to populate some variables (imgs, types, json):

printf -v imgs ',"%s"' "${imgArray[@]:1}"            # preface 2nd-nth elements with a comma
imgs="\"${imgArray[0]}\"${imgs}"                     # add 1st element

printf -v types ',"%s"' "${typeArray[@]:1}"          # preface 2nd-nth elements with a comma
types="\"${typeArray[0]}\"${types}"                  # add 1st element

printf -v json '{"itemName":"%s","images":[%s],"type":[%s],"link":"/"}' "$itemName" "$imgs" "$types"

This generates:

$ typeset -p json
declare -- json="{\"itemName\":\"itemname\",\"images\":[\"url1\",\"url2\",\"url3\"],\"type\":[\"all\",\"hats\"],\"link\":\"/\"}"

$ echo "$json"
{"itemName":"itemname","images":["url1","url2","url3"],"type":["all","hats"],"link":"/"}

答案2

得分: 1

安装 jq 并使用它生成 JSON 文件。Bash 和 sed 不是用于操作 JSON 的正确工具。

在您的情况下,构建 JSON 不能在单一步骤中完成。让我们从图像列表开始:

for i in "${ITEMS[@]}"; do
  echo "$i"
done |
jq -sRc '. | rtrimstr("\n") | split("\n")'

上面的 for 命令将 $ITEMS Bash 数组变量的项目逐行列出。输出通过管道传递给 jq,并按以下方式进行处理:

  • 命令行选项 -s 告诉它将所有输入数据一次性吸入数组,而不是单独处理每行;
  • 选项 -R 表示“原始输入”;与 s 结合使用,它告诉 jq 读取整个输入为一个大字符串,并仅对其应用程序一次;
  • 选项 -c 告诉它生成紧凑的输出(整个 JSON 放在一行上,没有额外的空格)。

短选项可以组合成单个单词。-sRc-s -R -c 相同。

jq 程序:

.                  # “.” 总是表示“当前项目”;这意味着这里的输入字符串
|                  # 将上一个筛选器生成的值传递给下一个筛选器
rtrimstr("\n")     # 去除当前项目右侧的“\n”
|                  # 将(已修剪的字符串)传递给下一个项目
split("\n")        # 使用换行符作为分隔符拆分输入字符串

jq 程序基本上执行了与 for 循环相反的操作,但非常重要的是,它处理原始数据并生成 JSON(字符串数组)。

假设数组初始化如下:

ITEMS=(foo bar "baz boo")

... 上面的脚本会生成:

["foo","bar","baz boo"]

这使我们能够编写一个更大的命令行,以执行您所需的操作:

jq -n \
  --arg name "$itemName" \
  --argjson images "$(for img in "${imgArray[@]}"; do echo $img; done | jq -sRc '. | rtrimstr("\n") | split("\n)')" \
  --argjson types "$(for type in "${typeArray[@]}"; do echo $type; done | jq -sRc '. | rtrimstr("\n") | split("\n)')" \
  '{"itemName": $name, "images": $images, "type": $types, "link": "/"}'

命令行选项:

  • -n 告诉 jq 不要从 stdin 读取任何内容;
  • --arg 定义一个变量(name),其值是原始字符串,下一个参数);Bash 通过展开 "$itemName" 计算该参数;
  • --argjson 定义一个变量(images),其值为 JSON;jq 在执行程序之前解析此 JSON;“$(...)”产生括号之间命令的评估结果,结果是一个单词;该命令如上所述,从 Bash 数组变量生成一个编码为 JSON 的数组;
  • {"itemName": $name, "images": $images, "type": $types, "link": "/}jq 程序;它生成一个对象,其键为 itemNameimagetypelink,其值是使用 --arg--argjson 设置的 jq 变量的值;link 的值是字符串文字/

总的来说,这个调用 jq 三次的大命令行会执行您需要的操作,您无需担心尾随逗号或其他 JSON 细节。您只需正确地将所有内容包装在引号中。

在线检查它 here


更新

因为将 imgArray 渲染成编码为字符串数组的 JSON 的代码在子 shell 中运行,我们可以更改 IFS,并使用单个 echo 代替 for 循环以更快地获得相同的结果:

IFS=$'\n'; echo "${imgArray[*]}"

完整脚本变为:

jq -n \
  --arg name "$itemName" \
  --argjson images "$(IFS=$'\n'; echo "${imgArray[*]}" | jq -sRc '. | rtrimstr("\n") | split("\n)')" \
  --argjson types "$(IFS=$'\n'; echo "${typeArray[*]}" | jq -sRc '. | rtrimstr("\n") | split("\n)')" \
  '{"itemName": $name, "images": $images, "type": $types, "link": "/"}'

它具有相同的输出,但更短,应该运行得更快。在线检查它 [here](https://tio.run/##nU89b8IwFNz9K05WJCcoARW2oAwsSF060LHpYJCVGGwDthk

英文:

Install jq and use it to generate JSON files. Bash and sed are not the right tools to manipulate JSON.

In your case, building the JSON cannot be done in a single step. Let's start with the list of images:

for i in "${ITEMS[@]}"; do
  echo "$i"
done |
jq -sRc '. | rtrimstr("\n") | split("\n")'

The for command above lists the items of the $ITEMS Bash array variable one per line. The output is piped to jq that processes it as follows:

  • the command line option -s tells it to "slurp" all the input data into an array instead of processing each line individually;
  • the option -R means "raw input"; combined with s it tells jq to read the whole input in a big string and apply the program only once to it;
  • the option -c tells it to produce compact output (the entire JSON on a single line, without extra spaces).

The short options can be combined into a single word. -sRc is the same as -s -R -c.

The jq program:

.                  # `.` always means "the current item"; this means the input string here
|                  # pipe the value produced by the previous filter to the next filter
rtrimstr("\n")     # trim "\n" on the right side of the current item
|                  # pass (the trimmed string) to the next item
split("\n")        # split the input string using newlines as the separator

The jq program basically does the opposite of the for loop but, very important, it processes raw data and produces a JSON (an array of strings).

Assuming the array is initialized as:

ITEMS=(foo bar "baz boo")

... the script above produces:

["foo","bar","baz boo"]

This allows us to write a larger command line that does what you need:

jq -n \
  --arg name "$itemName" \
  --argjson images "$(for img in "${imgArray[@]}"; do echo $img; done | jq -sRc '. | rtrimstr("\n") | split("\n")')" \
  --argjson types "$(for type in "${typeArray[@]}"; do echo $type; done | jq -sRc '. | rtrimstr("\n") | split("\n")')" \
  '{ itemName: $name, images: $images, type: $types, link: "/" }'

The command line options:

  • -n tells jq to not read anything from stdin;
  • --arg defines a variable (name) whose value is a raw string, the next argument); Bash computes that argument by expanding "$itemName";
  • --argjson defines a variable (images) whose value is a JSON; jq parses this JSON before starting the execution of the program; "$(...)" produces the evaluation of the command between parentheses and the result is a single word; the command is the one described above, that produces an array encoded as JSON from a Bash array variable;
  • { itemName: $name, images: $images, type: $types, link: "/" } is the jq program; it produces an object having the keys itemName, image, type and link, whose values are produced from the values of the jq variables set using --arg and --argjson; the value of link is the string literal /.

All in all, this big command line that invokes jq three times does what you need and you don't have to worry about trailing commas or other JSON details. All you need to care is about wrapping everything in quotes properly.

Check it online.


Update

Because the code that renders imgArray into a JSON that encodes an array of strings runs in a sub-shell, we can change the IFS and use a single echo instead of a for loop to get the same outcome faster:

IFS=$'\n'; echo "${imgArray[*]}"

The complete script becomes:

jq -n \
  --arg name "$itemName" \
  --argjson images "$(IFS=$'\n'; echo "${imgArray[*]}" | jq -sRc '. | rtrimstr("\n") | split("\n")')" \
  --argjson types "$(IFS=$'\n'; echo "${typeArray[*]}" | jq -sRc '. | rtrimstr("\n") | split("\n")')" \
  '{ itemName: $name, images: $images, type: $types, link: "/" }'

It has the same output but it's shorter and it should run faster. Check it oneline.

huangapple
  • 本文由 发表于 2023年3月21日 01:39:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/75793544-2.html
匿名

发表评论

匿名网友

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

确定