How to fix missing json block separator - json

I'm trying to convert 7z file content list to json and can't fix missing separator between output converted blocks.
I'm little bit newbie in json conversion, but found that jq could do the job.
I read the jq documentation and found examples inside here and there also elsewhere without solution.
Please find the use case:
The command line:
jq -f pf_7z.jq -R
The input file demo.lst:
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2018-06-23 14:02:16 D.... 0 0 Installer
2018-06-23 14:02:16 ..... 3381 1157 Installer\Readme
2018-06-23 14:02:16 ..... 4646 1157 Installer\License.txt
2018-06-23 14:02:16 ..... 138892 136152 Installer\Setup.exe
The filter file pf7z.jq:
def parse:
def parse_line:
. | map(match("(\\d+-\\d+-\\d+) (\\d+:\\d+:\\d+) (D|.).* +(\\d+) +(\\d+) +(.*\\\\)([^\\\\]*)\\.(.*)")) | .[] |
({
"date" :(.captures[0].string),
"time" :(.captures[1].string),
"attr" :(.captures[2].string),
"size" :(.captures[3].string),
"path" :(.captures[5].string),
"name" :(.captures[6].string),
"extn" :(.captures[7].string)
});
split("\n") | ( {} + (parse_line));
parse
The expected result should be:
{
"date": "2018-06-23",
"time": "14:02:16",
"attr": ".",
"size": "4646",
"path": "Installer\",
"name": "License",
"extn": "txt"
},
{
"date": "2018-06-23",
"time": "14:02:16",
"attr": ".",
"size": "138892",
"path": "Installer\",
"name": "Setup",
"extn": "exe"
}
And I only got :
{
"date": "2018-06-23",
"time": "14:02:16",
"attr": ".",
"size": "4646",
"path": "Installer\",
"name": "License",
"extn": "txt"
}
{
"date": "2018-06-23",
"time": "14:02:16",
"attr": ".",
"size": "138892",
"path": "Installer\",
"name": "Setup",
"extn": "exe"
}
without the comma separator between blocks.
Thanks ;-)

Your def for parse_line produces a stream of JSON entities, whereas you evidently want a JSON array. Using your regex, you could write:
def parse:
def parse_line:
match("(\\d+-\\d+-\\d+) (\\d+:\\d+:\\d+) (D|.).* +(\\d+) +(\\d+) +(.*\\\\)([^\\\\]*)\\.(.*)")
| .captures
| map(.string)
| { "date" :.[0],
"time" :.[1],
"attr" :.[2],
"size" :.[3],
"path" :.[5],
"name" :.[6],
"extn" :.[7] } ;
[inputs | parse_line];
parse
Invocation
jq -nR -f 7z.jq 7z.txt
Alternative regex
The regex fragment (D|.).* does not make much sense.
You should consider replacing it by (.)[^ ]* or some such.
A simpler solution
def parse_line:
capture("(?<date>\\d+-\\d+-\\d+) "
+ "(?<time>\\d+:\\d+:\\d+) "
+ "(?<attr>.)[^ ]* +"
+ "(?<size>\\d+) +\\d+ +"
+ "(?<path>.*\\\\)"
+ "(?<name>[^\\\\]*)\\."
+ "(?<extn>.*)");
[inputs | parse_line]
An alternative approach
From the comment about JSONEdit, it seems likely to me that your overall approach might be suboptimal. Have you considered using jq rather than jq with JSONEdit?

Related

jq - Select Random object from each array and only return certain values in each object of filtered arrays

Overall Goals:
I take user input, as country and/or city variables, then use those variables to filter down the JSON data to only the relays that meet the users choice. (Probably do some regex in JQ when selecting with the user input variables, to make sure someone can input New York and it would match New York, NY)
I then want to select a random relay in the relay array, or 1 random relay in each cities relay array, which is where I am getting stuck.
After I get a random relay in that/each array, I want to print certain values based on the key (i.e. ipvr_addr_in and public_key), in a csv format (for now).
If there are multiple relays at this point, I would iterate through them to find the lowest latency, sort the list, choose the lowest and pass that IP,key pair on to the final configuration.
Originally, I thought I'd just grab the first object in the array and use that:
country_choice=UK
city_choice=London
if [[ -z "$city_choice" ]]; then
jq --arg country_choice "$country_choice" -r '.countries[] | select(.name==$country_choice) | .cities[] | .relays[:1] | .ipv4_addr_in+","+.public_key' testdata.json
else
jq --arg country_choice "$country_choice" --arg city_choice "$city_choice" -r '.countries[] | select(.name==$country_choice) | .cities[] | select(.name==$city_choice) | .relays[:1] | .ipv4_addr_in+","+.public_key' testdata.json
fi
But that resulted in the JQ error: Cannot index array with string "public_key"
I am still very new to JQ and JSONs, so I am unfamiliar with why I can't print out specific values after filtering down to a specific object of several arrays.
Can anyone help me out here? I can't seem to find anything online about "selecting random" in jq.
Also any help with some of my other overall goals would be very much appreciated, I'm no coding guru.
An abridged test dataset (testdata.json):
{
"countries": [
{
"name": "UK",
"code": "gb",
"cities": [
{
"name": "London",
"code": "lon",
"relays": [
{
"hostname": "gb4-wireguard",
"ipv4_addr_in": "141.98.252.130",
"ipv6_addr_in": "2a03:1b20:7:f011::a01f",
"public_key": "IJJe0TQtuQOyemL4IZn6oHEsMKSPqOuLfD5HoAWEPTY="
},
{
"hostname": "gb5-wireguard",
"ipv4_addr_in": "141.98.252.222",
"ipv6_addr_in": "2a03:1b20:7:f011::a02f",
"public_key": "J57ba81Q8bigy9RXBXvl0DgABTrbl81nb37GuX50gnY="
},
{
"hostname": "gb-lon-wg-101",
"ipv4_addr_in": "146.70.119.66",
"ipv6_addr_in": "2001:ac8:31:f007::a39f",
"public_key": "MPZX0ZQtB5r1pmcvIcsAt1AMvenios2ICBz9rjbN/l4="
}
]
},
{
"name": "Manchester",
"code": "mnc",
"relays": [
{
"hostname": "gb45-wireguard",
"ipv4_addr_in": "146.70.133.34",
"ipv6_addr_in": "2001:ac8:8b:2b::a45f",
"public_key": "v+dOPx0FM8lGCjJ7m/7miWy67PGuazYYzvJoeMb97n4="
},
{
"hostname": "gb46-wireguard",
"ipv4_addr_in": "146.70.133.66",
"ipv6_addr_in": "2001:ac8:8b:2c::a46f",
"public_key": "2bciRobW0TPtjrZ2teilr+7PjyiBMUGfixvAKOE52Xo="
},
{
"hostname": "gb-mnc-wg-001",
"ipv4_addr_in": "146.70.133.98",
"ipv6_addr_in": "2001:ac8:8b:2d::a47f",
"public_key": "Q2khJLbTSFxmppPGHgq2HdxMQx7CczPZCgVpYZMoNnM="
}
]
}
]
},
{
"name": "USA",
"code": "us",
"cities": [
{
"name": "New York, NY",
"code": "nyc",
"relays": [
{
"hostname": "us97-wireguard",
"ipv4_addr_in": "86.106.143.210",
"ipv6_addr_in": "2a0d:5600:24:a94::a97f",
"public_key": "5fzEFqyRqc6qa1QPngIBK1gmWc0ex1Bpot/f6RqZPmc="
},
{
"hostname": "us98-wireguard",
"ipv4_addr_in": "86.106.143.223",
"ipv6_addr_in": "2a0d:5600:24:a95::a98f",
"public_key": "bo50ppMvVlNG4S6zqgd/J5l1Ce7Og89u+wR10OvJrQ4="
},
{
"hostname": "us99-wireguard",
"ipv4_addr_in": "86.106.143.236",
"ipv6_addr_in": "2a0d:5600:24:a96::a99f",
"public_key": "EPLh6pVel06dND8cE4Prix9GP4hGLYNhQhn5mSN2yzM="
}
]
},
{
"name": "Seattle, WA",
"code": "sea",
"relays": [
{
"hostname": "us274-wireguard",
"ipv4_addr_in": "138.199.43.78",
"ipv6_addr_in": "2a02:6ea0:d80b:2::b74f",
"public_key": "ujasJmDuU0t4y6JmBLrdDxakKuaHvPRupRDfyywSWyw="
},
{
"hostname": "us-sea-wg-001",
"ipv4_addr_in": "138.199.43.91",
"ipv6_addr_in": "2a02:6ea0:d80b:3::b75f",
"public_key": "bZQF7VRDRK/JUJ8L6EFzF/zRw2tsqMRk6FesGtTgsC0="
},
{
"hostname": "us-sea-wg-003",
"ipv4_addr_in": "138.199.43.65",
"ipv6_addr_in": "2a02:6ea0:d80b:1::b73f",
"public_key": "4ke8ZSsroiI6Sp23OBbMAU6yQmdF3xU2N8CyzQXE/Qw="
}
]
}
]
}
]
}
.relays[1:] gives you an array of all but the first item. Use .relays[0] instead to get only the first item:
.countries[] | select(.name==$country_choice)
| .cities[] | select(.name==$city_choice)
| .relays[0] | .ipv4_addr_in+","+.public_key
141.98.252.130,IJJe0TQtuQOyemL4IZn6oHEsMKSPqOuLfD5HoAWEPTY=
Demo
As for a random value, jq does not have a random number generator. You could, however, (poorly) emulate one based on the current time using now, or import one generated outside using --arg or --argjson.
Here's one very simple jq-only implementation of your script importing the $RANDOM variable from bash, which "expands to a random integer between 0 and 32767" (so make sure your arrays are not longer than that):
country_choice=UK # setting the country
city_choice= # omitting the city
jq --arg country_choice "$country_choice" --argjson country_random $RANDOM \
--arg city_choice "$city_choice" --argjson city_random $RANDOM \
--argjson relay_random $RANDOM -r \
'
.countries
| if $country_choice == ""
then .[$country_random % length]
else .[] | select(.name == $country_choice) end
| .cities
| if $city_choice == ""
then .[$city_random % length]
else .[] | select(.name == $city_choice) end
| .relays | .[$relay_random % length]
| .ipv4_addr_in + "," + .public_key
'
146.70.133.98,Q2khJLbTSFxmppPGHgq2HdxMQx7CczPZCgVpYZMoNnM=
Demo (with faked random numbers)
Since the question envisions requiring several or an a priori unknown number of random values, the following illustration may be of interest.
< /dev/urandom tr -cd '0-9' | fold -w 1 | jq -MRcnr '
# Output: a prn in range(0;$n) where $n is `.`
def prn:
if . == 1 then 0
else . as $n
| ([1, (($n-1)|tostring|length)]|max) as $w
| [limit($w; inputs)] | join("") | tonumber
| if . < $n then . else ($n | prn) end
end;
# An illustration - 10 selections at random with replacement
[range(0;10) | ["a", "b", "c"] | .[length|prn]]
'
Notice that STDIN is used for the entropy source; file inputs would have to be handled using command-line options such as --rawfile or --slurpfile.
In fact, if one requires an a priori unknown number of random values, then there really isn't any other practical choice when using the current version of jq.

Extract data from JSON and insert it as new by using jq

I have some database in JSON file, I had already sort and remove some data from object by using ./jq
But I'm stuck at adding new variables in object.
Here is a part of my JSON file:
{
"Name": "Forrest.Gump.1994.MULTi.1080p.AMZN.WEB-DL.DDP5.1.H264-Ao",
"ID": "SMwIkBoC2blXeWnBa9Hjge9YPs90"
},
{
"Name": "Point.Blank.2019.MULTi.1080p.NF.WEB-DL.DDP5.1.x264-Ao",
"ID": "OZI4mOuBXuJ7b89FLgXJoozyhHe9"
},
{
"Name": "The.Incredible.Hulk.2008.MULTi.2160p.UHD.BluRay.REMUX.HDR.HEVC.DTS-HD.MA.7.1",
"ID": "jZzR4_B_vjm593cYKR7j97XAMv6d"
},
Is it possible by using jq and for example RegExp to extract some data and insert it as new variable in object, I wish to achive something like this:
{
"Name": "Forrest.Gump.1994.MULTi.1080p.AMZN.WEB-DL.DDP5.1.H264-Ao",
"ID": "SMwIkBoC2blXeWnBa9Hjge9YPs90",
"Year": "1994",
"Res": "1080p"
},
{
"Name": "Point.Blank.2019.MULTi.1080p.NF.WEB-DL.DDP5.1.x264-Ao",
"ID": "OZI4mOuBXuJ7b89FLgXJoozyhHe9",
"Year": "2019",
"Res": "1080p"
},
{
"Name": "The.Incredible.Hulk.2008.MULTi.2160p.UHD.BluRay.REMUX.HDR.HEVC.DTS-HD.MA.7.1",
"ID": "jZzR4_B_vjm593cYKR7j97XAMv6d",
"Year": "2008",
"Res": "2160p"
},
Thanks in advance
Here's one solution that assumes for simplicity that the fragment you've shown comes from an array:
map( . as $in
| .Name | capture(".*[.](?<year>[12][0-9]{3})[.](?<rest>.*)")
| .year as $year
| (.rest | split(".") | .[1]) as $res
| $in + {Year: $year, Res: $res} )
Hopefully, once you're familiar with some jq basics, such as map, capture, and the EXP as $var syntax, the above will be more-or-less self-explanatory.
As a one-liner
Here's the same thing but as a one-liner:
map(. + (.Name | capture(".*[.](?<Year>[12][0-9]{3})[.](?<Res>.*)") | {Year, Res: (.Res | split(".")[1])}))

Transform json to ini files using jq + bash

I'm trying to transform json (array of objects) to ini files:
[
{
"connection": {
"id": "br0",
"uuid": "ab1dd903-4786-4c7e-a4b4-3339b144d6c7",
"stable-id": "",
"type": "bridge",
"interface-name": "br0",
"autoconnect": "no",
"autoconnect-priority": "0",
"autoconnect-retries": "-1",
"auth-retries": "-1",
"timestamp": "44444",
"read-only": "no",
"permissions": "",
"zone": "WAN",
"master": "",
"slave-type": "",
"autoconnect-slaves": "1",
"secondaries": "",
"gateway-ping-timeout": "0",
"metered": "unknown",
"lldp": "default"
},
"ipv4": {
"method": "manual",
"dns": "192.168.1.1,192.168.2.1",
"dns-search": "",
"dns-options": " ",
"dns-priority": "0",
"addresses": "192.168.1.3/24",
"gateway": "",
"routes": "192.168.10.0/24 192.168.1.1",
"route-metric": "-1",
"route-table": "0",
"ignore-auto-routes": "no",
"ignore-auto-dns": "no",
"dhcp-client-id": "",
"dhcp-timeout": "0",
"dhcp-send-hostname": "yes",
"dhcp-hostname": "",
"dhcp-fqdn": "",
"never-default": "no",
"may-fail": "yes",
"dad-timeout": "-1"
}
},
{
"connection": {
...
},
}
]
OR
{
"connection": {
...
},
}
What i tried to do is:
1. Transform json to strings
data=$(jq -r 'def keyValue: to_entries[] | "[\(.key)]\\n\(.value | to_entries|map("\(.key)=\(.value)" ) | join("\\n") )\\n"; if type == "array" then keys[] as $k | "\(.[$k] | .connection.id)=\(.[$k] | keyValue)" elif type == "object" then "\(.connection.id)=\(. | keyValue)" else keys end' /tmp/json)
Provides:
br1=[connection]\nid=br1\nuuid=ab1dd903-4786-4c7e-a4b4-3339b144d6c7\nstable-id=\ntype=fff\ninterface-name=br0\nautoconnect=no\nautoconnect-priority=0\nautoconnect-retries=-1\nauth-retries=-1\ntimestamp=1525494904\nread-only=no\npermissions=\nzone=WAN\nmaster=\nslave-type=\nautoconnect-slaves=1\nsecondaries=\ngateway-ping-timeout=0\nmetered=unknown\nlldp=default\n
br1=[802-3-ethernet]\nport=\nspeed=0\nduplex=\nauto-negotiate=no\nmac-address=\ncloned-mac-address=\ngenerate-mac-address-mask=\nmac-address-blacklist=\nmtu=1500\ns390-subchannels=\ns390-nettype=\ns390-options=\nwake-on-lan=default\nwake-on-lan-password=\n....
2. Walk over strings in bash
while IFS="=" read -r key value; do [ "$oldkey" = "$key" ] && echo -en "$value" >> "/tmp/ini/$key" || echo -en "$value" > "/tmp/ini/$key" ; oldkey="$key"; done <<< "$data"
Gives:
[connection]
id=br1
uuid=ab1dd903-4786-4c7e-a4b4-3339b144d6c7
stable-id=
type=fff
interface-name=br0
autoconnect=no
autoconnect-priority=0
autoconnect-retries=-1
auth-retries=-1
timestamp=1525494904
read-only=no
permissions=
zone=WAN
master=
slave-type=
autoconnect-slaves=1
secondaries=
gateway-ping-timeout=0
metered=unknown
lldp=default
[ipv4]
method=manual
dns=192.168.1.1,192.168.2.1
dns-search=
dns-options=
dns-priority=0
addresses=192.168.1.3/24
gateway=
routes=192.168.10.0/24 192.168.1.1
route-metric=-1
route-table=0
ignore-auto-routes=no
ignore-auto-dns=no
dhcp-client-id=
dhcp-timeout=0
dhcp-send-hostname=yes
dhcp-hostname=
dhcp-fqdn=
never-default=no
may-fail=yes
dad-timeout=-1
I'm almost there! But is there possible to do it more "elegantly" and more performance way, avoiding pipes, external calls, etc.
Note: Moreover, mostly it should be done with jq + bash, because other processing tools like sed, awk is slower than i've done, but i do not reject them completely =)
P.S. - Main purpose of this transforming is the fast "bulk operation" to write ini files
To convert an array as shown to the ".ini" format could be accomplished by simply using this jq program:
def kv: to_entries[] | "\(.key)=\(.value)";
.[]
| to_entries[]
| "[\(.key)]", (.value|kv)
Putting this in a file, say program.jq, then assuming the JSON shown in the question (minus the "..." part) is in input.json, the following invocation:
jq -rf program.jq input.json
yields the corresponding ".ini" file.
If you want to ensure that the program will also handle the case when there is no enclosing array, you could modify the first line in the main program above to test whether the input is an array, so you'd have:
if type == "array" then .[] else . end
| to_entries[]
| "[\(.key)]", (.value|kv)
If the ultimate goal is to produce several .ini files, then we can reuse def kv as defined in the other answer:
def kv: to_entries[] | "\(.key)=\(.value)";
The driver program would however now be:
.[]
| [to_entries[] | "[\(.key)]", (.value|kv)]
| join("\n")
Running jq with this program then yields one JSON string for each item in the array. Using the example, the first such string would be:
"[connection]\nid=br0\nuuid=ab1dd903-4786-4c7e-a4b4-3339b144d6c7\nstable-id=\ntype=bridge\ninterface-name=br0\nautoconnect=no\nautoconnect-priority=0\nautoconnect-retries=-1\nauth-retries=-1\ntimestamp=44444\nread-only=no\npermissions=\nzone=WAN\nmaster=\nslave-type=\nautoconnect-slaves=1\nsecondaries=\ngateway-ping-timeout=0\nmetered=unknown\nlldp=default\n[ipv4]\nmethod=manual\ndns=192.168.1.1,192.168.2.1\ndns-search=\ndns-options= \ndns-priority=0\naddresses=192.168.1.3/24\ngateway=\nroutes=192.168.10.0/24 192.168.1.1\nroute-metric=-1\nroute-table=0\nignore-auto-routes=no\nignore-auto-dns=no\ndhcp-client-id=\ndhcp-timeout=0\ndhcp-send-hostname=yes\ndhcp-hostname=\ndhcp-fqdn=\nnever-default=no\nmay-fail=yes\ndad-timeout=-1"
You can then iterate over these newline-delimited strings.

create an object from an existing json file using 'jq'

I have a messages.json file
[
{
"id": "title",
"description": "This is the Title",
"defaultMessage": "title",
"filepath": "src/title.js"
},
{
"id": "title1",
"description": "This is the Title1",
"defaultMessage": "title1",
"filepath": "src/title1.js"
},
{
"id": "title2",
"description": "This is the Title2",
"defaultMessage": "title2",
"filepath": "src/title2.js"
},
{
"id": "title2",
"description": "This is the Title2",
"defaultMessage": "title2",
"filepath": "src/title2.js"
},
]
I want to create an object
{
"title": "Dummy1",
"title1": "Dummy2",
"title2": "Dummy3",
"title3": "Dummy4"
}
from the top one.
So far I have
jq '.[] | .id' src/messages.json;
And it does give me the IDs
How do I add some random text and make the new object as above?
Can we also create a new JSON file and write the newly created object onto it using jq?
Your output included "title3" so I'll assume that you intended that the second occurrence of "title2" in the input was supposed to refer to "title3".
With this assumption, the following jq program seems to do what you want:
map( .id )
| . as $in
| reduce range(0;length) as $i ({};
. + {($in[$i]): "dummy\(1+$i)"})
In words, extract the values of .id, and then turn each into an object of the form: {(.id) : "dummy\(1+$i)"}
This uses string interpolation, and produces:
{
"title": "dummy1",
"title1": "dummy2",
"title2": "dummy3",
"title3": "dummy4"
}
reduce-free solution
map(.id )
| [., [range(0;length)]]
| transpose
| map( {(.[0]): "dummy\(.[1]+1)"})
| add
Output
Can we also create a new json file and write the newly created object onto it using jq?
Yes, just use output redirection:
jq -f program.jq messages.json > output.json
Addendum
I want a parent object "de" to the already created json file objects
You could just pipe either of the above solutions to: {de: .}

How to display two values serially with jq?

I am straggling some time with jq to parse json file. I have to grab ID and Name in the same line so I can do some logic after.
{
"elements": [{
"id": "e20a9cd8-8683-4986-b6c0-e5fbf51cbf7f",
"name": "Mike",
"components": [{
"id": "15f959fc-6d2d-451a-a59e-430a05a1852c",
"pid": "ZZZ1"
}],
"tenantIds": null,
"productIds": null
},
{
"id": "d892f2eb-d7f3-49f8-9176-2113351cccf8",
"name": "Steve",
"components": [{
"id": "0c44c917-e0e5-4fa3-b87c-89f9ac0815b4",
"pid": "XXX3"
}],
"tenantIds": null,
"productIds": null
}
]
}
With jq '{elements}[] | .[].id I am getting ID's but I can't find solution how to add appropriate name beside ID. I tried something like
{elements}[] | .[].id + " " + .[].name
and
{elements}[] | .[].id + .[].name
but it's not what I expected.
I want to get:
15f959fc-6d2d-451a-a59e-430a05a1852c Mike
d892f2eb-d7f3-49f8-9176-2113351cccf8 Steve
Any suggestion?
Thanks!
Apply string concatenation:
jq '.elements[] | .id +" "+ .name' file
The output:
"e20a9cd8-8683-4986-b6c0-e5fbf51cbf7f Mike"
"d892f2eb-d7f3-49f8-9176-2113351cccf8 Steve"
To output without double quotes use -r (--raw-output) option:
jq '.elements[] | .id +" "+ .name' -r file
e20a9cd8-8683-4986-b6c0-e5fbf51cbf7f Mike
d892f2eb-d7f3-49f8-9176-2113351cccf8 Steve