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

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

How to remove trailing commas from array in JSON

问题

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

这是 printf 语句,用于格式化 JSON。你提到的问题是由于在 printf 中的 %s, 语句导致尾随逗号。你可以使用 sed 命令来删除数组中的最后一个逗号。

英文:

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

以下是翻译好的内容:

假设:

  • 两个数组的第一个元素的索引为 0

示例输入:

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

**注意:**虽然可能可以使用 JSON 工具(例如 jq)来执行此操作,但我将专注于一些常规的 bash 功能...

一般的方法是将第一个元素单独打印,然后在第二个到第 n 个元素之前加上逗号。

将当前的 printf 调用分为两部分:

# 这些:

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

# 变成这些:

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

这仍然需要执行 3 次子进程调用。

消除子进程的一个想法是使用(bashprintf 内建命令来填充一些变量(imgstypesjson):

printf -v imgs ',"%s"' "${imgArray[@]:1}"            # 在第 2 到第 n 个元素之前加上逗号
imgs="\"${imgArray[0]}\"${imgs}"                     # 添加第一个元素

printf -v types ',"%s"' "${typeArray[@]:1}"          # 在第 2 到第 n 个元素之前加上逗号
types="\"${typeArray[0]}\"${types}"                  # 添加第一个元素

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

这将生成:

$ 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

以下是您要求的翻译的部分:

"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](https://tio.run/##nU89b8IwFNz9K05WJCcoARW2oAwsSF060LHpYJCVGGwDthkQ8NuDDQSkSl1

英文:

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.html
匿名

发表评论

匿名网友

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

确定