Convert a txt file into JSON - json

I need to convert a simple txt list into a specific json format.
My list looks like this :
server1
server2
server3
server4
I need to have a JSON output that would look like this :
{ "data": [
{ "{SERVER}":"server1" },
{ "{SERVER}":"server2" },
{ "{SERVER}":"server3" },
{ "{SERVER}":"server4" }
]}
I was able to generate this with a bash script but I don't know how to remove the comma for the last line. The list is dynamic and can have a different amount of servers every time the script is run.
Any tip please ?
EDIT : my current code :
echo "{ "data": [" > /tmp/json_output
for srv in `cat /tmp/list`; do
echo "{ \"{SERVER}\":\"$srv\" }," >> /tmp/json_output
done
echo "]}" >> /tmp/json_output
I'm very new at this, sorry if I sound noobish.

This is very easy for Xidel:
xidel -s input.txt -e '{"data":[x:lines($raw) ! {"{SERVER}":.}]}'
{
"data": [
{
"{SERVER}": "server1"
},
{
"{SERVER}": "server2"
},
{
"{SERVER}": "server3"
},
{
"{SERVER}": "server4"
}
]
}
x:lines($raw) is a shorthand for tokenize($raw,'\r\n?|\n'). It creates an array of every new line.
In human terms you can see x:lines($raw) ! {"{SERVER}":.} as "create a JSON object for every new line".
See also this xidelcgi demo.

I would use jq for this.
$ jq -nR 'reduce inputs as $i ([]; .+[$i]) | map ({"{SERVER}": .}) | {data: .}' tmp.txt
{
"data": [
{
"{SERVER}": "server1"
},
{
"{SERVER}": "server2"
},
{
"{SERVER}": "server3"
},
{
"{SERVER}": "server4"
}
]
}
(It seems to me there should be an easier way to produce the array ["server1", "server2", "server3", "server4"] to feed to the map filter, but this is functional.)
Breaking this down piece by piece, we first create an array of your server names.
$ jq -nR 'reduce inputs as $item ([]; .+[$item])' tmp.txt
[
"server1",
"server2",
"server3",
"server4"
]
This array is fed to a map filter that creates an array of objects with the {SERVER} key:
$ jq -nR 'reduce inputs as $item ([]; .+[$item]) | map ({"{SERVER}": .})' tmp.txt
[
{
"{SERVER}": "server1"
},
{
"{SERVER}": "server2"
},
{
"{SERVER}": "server3"
},
{
"{SERVER}": "server4"
}
]
And finally, this is used to create an object that maps the key data to the array of objects, as shown at the top.

As others said a tool like jq should better be used.
Alternatively, you could use an array and determine whether the element you process is the last or not, for example your code could be :
declare -a servers
servers=($(cat /tmp/list))
pos=$(( ${#servers[*]} - 1 ))
last=${servers[$pos]}
echo "{ "data": [" > /tmp/json_output
for srv in "${servers[#]}"; do
if [[ $srv == $last ]]
then
echo "{ \"{SERVER}\":\"$srv\" }" >> /tmp/json_output
else
echo "{ \"{SERVER}\":\"$srv\" }," >> /tmp/json_output
fi
done
echo "]}" >> /tmp/json_output

You can do this using Python 3 :
#!/usr/local/bin/python3
import os
import json
d = []
with open('listofservers.txt', 'r', encoding='utf-8') as f:
for server in f:
d.append({'{Server}' : server.rstrip("\n")})
print(json.dumps({'data': d}, indent=" "))
Which will print :
{
"data": [
{
"{Server}": "server1"
},
{
"{Server}": "server2"
},
{
"{Server}": "server3"
},
{
"{Server}": "server4"
}
]
}

Related

jq to replace array key values from file

I have a json file with this data:
{
"data": [
{
"name": "table",
"values": [
"This is old data",
"that needs to be",
"replaced."
]
}
]
}
But my challege here is I need to replace that values array with words in a text or csv file:
this
this
this
is
is
an
an
array
My output needs to have (although I could probably get away with the words all on one line...):
"values": [
"this this this",
"is is",
"an an",
"array"
],
Is this possible with only jq? Or would I have to get awk to help out?
I already started down the awk road with:
awk -F, 'BEGIN{ORS=" "; {print "["}} {print $2} END{{print "]"}}' filename
But I know there is still some work here...
And then I came across jq -Rn inputs. But I haven't figured out how or if I can get the desired result.
Thanks for any pointers.
Assuming you have a raw ASCII text file named file and an input JSON file, you could do
jq --rawfile txt file '.data[].values |= ( $txt | split("\n")[:-1] | group_by(.) | map(join(" ")) )' json
produces
{
"data": [
{
"name": "table",
"values": [
"an an",
"array",
"is is",
"this this this"
]
}
]
}
You can use jq and awk.
Given:
$ cat file
{
"data": [
{
"name": "table",
"values": [
"This is old data",
"that needs to be",
"replaced."
]
}
]
}
$ cat replacement
this
this
this
is
is
an
an
array
First create a string for the replacement array (awk is easy to use here):
ins=$(awk '!s {s=last=$1; next}
$1==last{s=s " " $1; next}
{print s; s=last=$1}
END{print s}' replacement | tr '\n' '\t')
Then use jq to insert into the JSON:
jq --rawfile txt <(echo "$ins") '.data[].values |= ( $txt | split("\t")[:-1] )' file
{
"data": [
{
"name": "table",
"values": [
"this this this",
"is is",
"an an",
"array"
]
}
]
}
You can also use ruby to process both files:
ruby -r json -e '
BEGIN{ ar=File.readlines(ARGV[0])
.map{|l| l.rstrip}
.group_by{|e| e}
.values
.map{|v| v.join(" ")}
j=JSON.parse(File.read(ARGV[1]))
}
j["data"][0]["values"]=ar
puts JSON.pretty_generate(j)' txt file
# same output...

jq: add new elements to list from other file

I have two json files.
File 1:
{
"data": {
"items": []
}
}
File 2:
[
{
"name": "first name",
"path": [{
"matcher": "exact",
}]
},
{
...
}
]
I want to add all items from File 2 to the .data.items list in File 1.
How can I accomplish this?
Thanks in advance!
if you're willing to normalize the detail file:
items=$(jq -c ' . | { data: { items: . }} ' 2.json)
jq -s '{ data: { items: map(.data.items[])}}' 1.json <(echo $items)
Assuming both files contain valid JSON, you could do worse than:
jq --argfile extra 2.json '.data.items += $extra' 1.json

Merge json files - Concatenate into a single dict json file

here are 3 JSON files
File1
{
"component1": [
]
}
File2
{
"component2": [
]
}
File3
{
"component3": [
]
}
Don't find the jq command line that would give this JSON file as jq output:
{
"components": {
"component1": [
],
"component2": [
],
"component3": [
]
}
}
Many thanks for your support
Best Regards.
Iterate over the input objects one a time from inputs and append it to the components using the reduce function
jq -n 'reduce inputs as $d (.; .components += $d )' file{1..3}.json
You can simply use add, e.g.
jq -s '{components: add}' file{1..3}.json
or:
jq -n '{components: [inputs]|add}' file{1..3}.json

How to loop through a json key that value is an array

I am somewhat new to bash. My Objective is to loop through the JSON and capture the user input and export it to environment variable.
This is the structure of the json. Any help will be appreciated!
{
"items": [{
"Gitlab": [
"GITLAB_URL",
"GITLAB_TOKEN",
"GITLAB_CHANNEL",
"GITLAB_SHOW_COMMITS_LIST",
"GITLAB_SHOW_MERGE_DESCRIPTION",
"GITLAB_DEBUG",
"GITLAB_BRANCHES"
]
},
{
"PagerDuty": [
"PAGERV2_API_KEY",
"PAGERV2_SCHEDULE_ID",
"PAGERV2_SERVICES",
"PAGERV2_DEFAULT_RESOLVER",
"PAGERV2_ENDPOINT",
"PAGERV2_ANNOUNCE_ROOM",
"PAGERV2_NEED_GROUP_AUTH",
"PAGERV2_LOG_PATH"
]
},
{
"SLack": [
"SLACK_TOKEN"
]
}
]
}
This is what I have so far
jq '.items[] | select( .Gitlab| startswith("first-block-"))' < configurations.json
for i in ${items[#]}
do
echo "Enter your " $i
read input
if [[ ! -z "$input" ]]; then
export $i=$input
fi
done

CSV to JSON using BASH

I am trying to covert the below csv into json format.
Africa,Kenya,NAI,281
Africa,Kenya,NAI,281
Asia,India,NSI,100
Asia,India,BSE,160
Asia,Pakistan,ISE,100
Asia,Pakistan,ANO,100
European Union,United Kingdom,LSE,100
This is the desired json format and I just cannot get to create it. I will post my work in progress below this.. Any help or direction would be appreciated...
{"name":"Africa",
"children":[
{"name":"Kenya",
"children":[
{"name":"NAI","size":"109"},
{"name":"NAA","size":"160"}]}]},
{"name":"Asia",
"children":[
{"name":"India",
"children":[
{"name":"NSI","size":"100"},
{"name":"BSE","size":"60"}]},
{"name":"Pakistan",
"children":[
{"name":"ISE","size":"120"},
{"name":"ANO","size":"433"}]}]},
{"name":"European Union",
"children":[
{"name":"United Kingdom",
"children":[
{"name":"LSE","size":"550"},
{"name":"PLU","size":"123"}]}]}
Work in Progress.
$1 is the file with the csv values pasted above.
#!/bin/bash
pcountry=$(head -1 $1 | cut -d, -f2)
cat $1 | while read line ; do
region=$(echo $line|cut -d, -f1)
country=$(echo $line|cut -d, -f2)
code=$(echo $line|cut -d, -f3-)
size=$(echo $line|cut -d, -f4)
if test "$pcountry" == "$country" ;
then
echo -e {\"name\":\"$region\", '\n' \"children\": [ '\n'{\"name\":\"$country\",'\n'\"children\": [ '\n' \{\"name\":\"NAI\",\"size\":\"$size\"\}
else
if test "$pregion" == "$region"
then :
else
echo -e ,'\n'{\"name\":\""$region\", '\n' \"children\": [ '\n'{\"name\":\"$country\",'\n'\"children\": [ '\n' \{\"name\":\"NAI\",\"size\":\"$size\"\},
pcountry=$country
pregion=$region
fi ; done
Problem is that I cannot seem to find a way to find out when a countries value ends.
As a number of the commenters have said, using the shell for this kind of conversion is a horrible idea. And, it would be nigh impossible to do it with just bash builtins; and shell scripts are used to combine standard unix commands like sed, awk, cut, etc. anyway. You should choose a better language that's built for that kind of iterative parsing/processing to solve your problem.
However, because it's late and I've had too much coffee, I threw together a bash script (with a few bits of sed thrown in for parsing help) that takes the example .csv data you have and outputs the JSON in the format you noted. Here's the script:
#! /bin/bash
# Initial input file format:
#
# Africa,Kenya,NAI,281
# Africa,Kenya,NAA,281
# Asia,India,NSI,100
# Asia,India,BSE,160
# Asia,Pakistan,ISE,100
# Asia,Pakistan,ANO,100
# European Union,United Kingdom,LSE,100
#
# Intermediate file format for parsing to JSON:
#
# Africa|Kenya:NAI=281
# Asia|India:BSE=160&NSI=100|Pakistan:ISE=100&ANO=100
# European Union|United Kingdom:LSE=100
#
# Call as:
#
# $ ./script INPUTFILE.csv >OUTPUTFILE.json
#
# temporary files for output/parsing
TMP="./tmp.dat"
TMP2="./tmp2.dat"
>$TMP
>$TMP2
# read through initial file and output intermediate format
while read line
do
region=$(echo $line | cut -d, -f1)
country=$(echo $line | cut -d, -f2)
code=$(echo $line | cut -d, -f3)
size=$(echo $line | cut -d, -f4)
# region record already started
if grep "^$region" $TMP 2>&1 >/dev/null ;then
>$TMP2
while read rec
do
if echo $rec | grep "^$region" 2>&1 >/dev/null
then
if echo "$rec" | grep "\|$country:" 2>&1 >/dev/null
then
echo "$rec" | sed -e 's/\('"$country"':[^\|][^\|]*\)/\1\&'"$code"'='"$size"'/' >>$TMP2
else
echo "$rec|$country:$code=$size" >>$TMP2
fi
else
echo $rec >>$TMP2
fi
done < $TMP
mv $TMP2 $TMP
else
# new region
echo "$region|$country:$code=$size" >>$TMP
fi
done < $1
# Parse through our intermediary format and output JSON to standard out
echo "["
country_count=$(cat $TMP | wc -l)
while read line
do
country=$(echo $line | cut -d\| -f1)
echo "{ \"name\": \"$country\", "
echo " \"children\": ["
region_count=$(echo $line | cut -d\| -f2- | sed -e 's/|/\n/g' | wc -l)
echo $line | cut -d\| -f2- | sed -e 's/|/\n/g' |
while read region
do
name=$(echo $region | cut -d: -f1)
echo " { \"name\": \"$name\", "
echo " \"children\": ["
code_count=$(echo $region | sed -e 's/^'"$name"'://' -e 's/&/\n/g' | wc -l)
echo $region | sed -e 's/^'"$name"'://' -e 's/&/\n/g' |
while read code_size
do
code=$(echo $code_size | cut -d= -f1)
size=$(echo $code_size | cut -d= -f2)
code_count=$((code_count - 1))
COMMA=""
if [ $code_count -gt 0 ]; then
COMMA=","
fi
echo " { \"name\": \"$code\", \"size\": \"$size\" }$COMMA "
done
echo " ]"
region_count=$((region_count - 1))
if [ $region_count -gt 0 ]; then
echo " },"
else
echo " }"
fi
done
echo " ]"
country_count=$((country_count - 1))
COMMA=""
if [ $country_count -gt 0 ]; then
COMMA=","
fi
echo "}$COMMA"
done < $TMP
echo "]"
exit 0
And, here's the resulting output from the above script:
[
{ "name": "Africa",
"children": [
{ "name": "Kenya",
"children": [
{ "name": "NAI", "size": "281" },
{ "name": "NAA", "size": "281" }
]
}
]
},
{ "name": "Asia",
"children": [
{ "name": "India",
"children": [
{ "name": "NSI", "size": "100" },
{ "name": "BSE", "size": "160" }
]
},
{ "name": "Pakistan",
"children": [
{ "name": "ISE", "size": "100" },
{ "name": "ANO", "size": "100" }
]
}
]
},
{ "name": "European Union",
"children": [
{ "name": "United Kingdom",
"children": [
{ "name": "LSE", "size": "100" }
]
}
]
}
]
Please don't use code like the above in any production environment.
Here is a solution using jq.
If filter.jq contains the following filter
reduce (
split("\n")[] # split string into lines
| split(",") # split data
| select(length>0) # eliminate blanks
) as [$c1,$c2,$c3,$c4] ( # convert to object
{} # e.g. "Africa": { "Kenya": {
; setpath([$c1,$c2,"name"];$c3) # "name": "NAI",
| setpath([$c1,$c2,"size"];$c4) # "size": "281"
) # }, }
| [ # then build final array of objects format:
keys[] as $k1 # [ {
| {name: $k1, children: ( # "name": "Africa",
.[$k1] # "children": {
| keys[] as $k2 # "name": "Kenya",
| {name: $k2, children:.[$k2]} # "children": { "name": "NAI", "size": "281" }
)} # ...
]
and data contains the sample data then the command
$ jq -M -Rsr -f filter.jq data
produces
[
{
"name": "Africa",
"children": {
"name": "Kenya",
"children": {
"name": "NAI",
"size": "281"
}
}
},
{
"name": "Asia",
"children": {
"name": "India",
"children": {
"name": "BSE",
"size": "160"
}
}
},
{
"name": "Asia",
"children": {
"name": "Pakistan",
"children": {
"name": "ANO",
"size": "100"
}
}
},
{
"name": "European Union",
"children": {
"name": "United Kingdom",
"children": {
"name": "LSE",
"size": "100"
}
}
}
]
You'd be much better off using a tool like xidel that can manipulate csv / raw text and understands JSON:
I'm going to assume so_24300508.csv :
Africa,Kenya,NAI,109
Africa,Kenya,NAA,160
Asia,India,NSI,100
Asia,India,BSE,60
Asia,Pakistan,ISE,120
Asia,Pakistan,ANO,433
European Union,United Kingdom,LSE,550
European Union,United Kingdom,PLU,123
(this is extracted from your JSON sample instead of the CSV sample you provided)
xidel -s so_24300508.csv --json-mode=deprecated --xquery '
[
let $csv:=x:lines($raw)
for $region in distinct-values($csv ! tokenize(.,",")[1])
return {
"name":$region,
"children":[
for $country in distinct-values($csv[starts-with(.,$region)] ! tokenize(.,",")[2]) return {
"name":$country,
"children":for $data in $csv[starts-with(.,$region) and contains(.,$country)]
let $value:=tokenize($data,",")
return {
"name":$value[3],
"size":$value[4]
}
}
]
}
]
'
(without --json-mode=deprecated replace [ ] with array{ })
See this code snippet for intermediate steps leading to this query.
Also see this online xidelcgi demo.
Output:
[
{
"name": "Africa",
"children": [
{
"name": "Kenya",
"children": [
{
"name": "NAI",
"size": "109"
},
{
"name": "NAA",
"size": "160"
}
]
}
]
},
{
"name": "Asia",
"children": [
{
"name": "India",
"children": [
{
"name": "NSI",
"size": "100"
},
{
"name": "BSE",
"size": "60"
}
]
},
{
"name": "Pakistan",
"children": [
{
"name": "ISE",
"size": "120"
},
{
"name": "ANO",
"size": "433"
}
]
}
]
},
{
"name": "European Union",
"children": [
{
"name": "United Kingdom",
"children": [
{
"name": "LSE",
"size": "550"
},
{
"name": "PLU",
"size": "123"
}
]
}
]
}
]