Handling multiple top level elements with jq - json

I'm trying to modify the deluge web.conf file with jq and I'm having some issues. The deluge web config seems to be invalid json
{
"file": 1,
"format": 1
}
{
"sidebar_show_zero": false,
"show_session_speed": false,
"pwd_sha1": "CHANGEME",
"show_sidebar": true,
"sessions": {},
"enabled_plugins": [],
"base": "/",
"first_login": true,
"theme": "gray",
"pkey": "ssl/daemon.pkey",
"default_daemon": "",
"cert": "test",
"session_timeout": 3600,
"https": false,
"interface": "0.0.0.0",
"sidebar_multiple_filters": true,
"pwd_salt": "salt",
"port": 8112
}
It has multiple top level elements which aren't separated by a comma so if I try to edit the file with jq using something like this jq '.pwd_sha1 = "NEW HASH"' web.conf I get the following
{
"file": 1,
"format": 1,
"pwd_sha1": "NEW HASH"
}
{
"sidebar_show_zero": false,
"show_session_speed": false,
"pwd_sha1": "NEW HASH",
"show_sidebar": true,
"sessions": {},
"enabled_plugins": [],
"base": "/",
"first_login": true,
"theme": "gray",
"pkey": "ssl/daemon.pkey",
"default_daemon": "",
"cert": "test",
"session_timeout": 3600,
"https": false,
"interface": "0.0.0.0",
"sidebar_multiple_filters": true,
"pwd_salt": "salt",
"port": 8112
}
jq is adding a new element to the first top level object and changing the second top level element's value. How can I get this to only change the existing item in the second top level element?

The web.conf you show is a stream of JSON entities. Fortunately for you, jq is stream-oriented, and it appears from your example that you could simply write:
jq 'if .pwd_sha1 then .pwd_sha1 = "NEW HASH" else . end' web.conf
In general, though, it might be more appropriate to write something with a more stringent test, e.g.
jq 'if type == "object" and has("pwd_sha1")
then .pwd_sha1 = "NEW HASH" else . end' web.conf
"changing the second top level element's value"
To edit the second top-level item only, you could use foreach inputs with the -n command-line option:
foreach inputs as $in (0; .+1;
if . == 2 then $in | .pwd_sha1 = "NEW_HASH"
else $in end)

Related

How to identify the specific true or false value from json assertion using jmeter?

The requirement is we have to identify "elements" and then check if its true or false.
{
"first": {
"second": [
{
"element": 1,
"elementrec": null,
"enabled": true,
"rec": null
},
{
"element": 2,
"elementrec": null,
"enabled": false,
"rec": null
},
{
"element": 3,
"elementrec": [
"3"
],
"enabled": true,
"rec": [
"3"
]
}
}
]
}
}
Above 3 element has either true or false. Would like to identify all 3 elements for true or false using different json assertion.
I don't know what do you mean by "identify", there are too many possible options so I'll come up with the shortest one:
Add JSR223 PostProcessor as a child of the request which returns the above JSON
Put the following code into "Script" area
new groovy.json.JsonSlurper().parse(prev.getResponseData()).first.second.each { item ->
log.info('Element: ' + item.element + ', enabled: ' + item.enabled)
}
Run your test
In jmeter.log file you will see "elements" with their "enabled" status
Demo:
More information:
Apache Groovy - Parsing and producing JSON
Apache Groovy - Why and How You Should Use It

Combine files in jq based on similar ID object and reform data

Preface: If the following is not possible with jq, then I completely accept that as an answer and will try to force this with bash.
I have two files that contain some IDs that, with some massaging, should be able to be combined into a single file. I have some content that I'll add to that as well (as seen in output). Essentially "mitre_test" should get compared to "sys_id". When compared, the "mitreid" from in2.json becomes technique_ID in the output (and is generally the unifying field of each output object).
Caveats:
There are some junk "desc" values placed in the in1.json that are there to make sure this is as programmatic as possible, and there are actually numerous junk inputs on the true input file I am using.
some of the mitre_test values have pairs and are not in a real array. I can split on those and break them out, but find myself losing the other information from in1.json.
Notice in the "metadata" for the output that is contains the "number" values from in1.json, and stored in a weird way (but the way that the receiving tool requires).
in1.json
[
{
"test": "Execution",
"mitreid": "T1204.001",
"mitre_test": "90b"
},
{
"test": "Defense Evasion",
"mitreid": "T1070.001",
"mitre_test": "afa"
},
{
"test": "Credential Access",
"mitreid": "T1556.004",
"mitre_test": "14b"
},
{
"test": "Initial Access",
"mitreid": "T1200",
"mitre_test": "f22"
},
{
"test": "Impact",
"mitreid": "T1489",
"mitre_test": "fa2"
}
]
in2.json
[
{
"number": "REL0001346",
"desc": "apple",
"mitre_test": "afa"
},
{
"number": "REL0001343",
"desc": "pear",
"mitre_test": "90b"
},
{
"number": "REL0001366",
"desc": "orange",
"mitre_test": "14b,f22"
},
{
"number": "REL0001378",
"desc": "pineapple",
"mitre_test": "90b"
}
]
The output:
[{
"techniqueID": "T1070.001",
"tactic": "defense-evasion",
"score": 1,
"color": "",
"comment": "",
"enabled": true,
"metadata": [{
"name": "DET_ID",
"value": "REL0001346"
}],
"showSubtechniques": true
},
{
"techniqueID": "T1204.001",
"tactic": "execution",
"score": 1,
"color": "",
"comment": "",
"enabled": true,
"metadata": [{
"name": "DET_ID",
"value": "REL0001343"
},
{
"name": "DET_ID",
"value": "REL0001378"
}],
"showSubtechniques": true
},
{
"techniqueID": "T1556.004",
"tactic": "credential-access",
"score": 1,
"color": "",
"comment": "",
"enabled": true,
"metadata": [{
"name": "DET_ID",
"value": "REL0001366"
}],
"showSubtechniques": true
},
{
"techniqueID": "T1200",
"tactic": "initial-access",
"score": 1,
"color": "",
"comment": "",
"enabled": true,
"metadata": [{
"name": "DET_ID",
"value": "REL0001366"
}],
"showSubtechniques": true
}
]
I'm assuming I have some splitting to do on mitre_test with something like .mitre_test |= split(",")), and there are some joins I'm assuming, but doing so causes data loss or mixing up of the data. You'll notice the static data in the output exists as well, but is likely easy to place in and as such isn't as much of an issue.
Edit: reduced some of the match IDs so that it is easier to look at while analyzing the in1 and in2 files. Also simplified the two inputs to have a similar structure so that the answer is easier to understand later.
The requirements are somewhat opaque but it's fairly clear that if the task can be done by computer, it can be done using jq.
From the description, it would appear that one of the unusual aspects of the problem is that the "dictionary" defined by in1.json must be derived by splitting the key names that are CSV (comma-separated values). Here therefore is a jq def that will do that:
# Input: a JSON dictionary for which some keys are CSV,
# Output: a JSON dictionary with the CSV keys split on the commas
def refine:
. as $in
| reduce keys_unsorted[] as $k ({};
if ($k|index(","))
then ($k/",") as $keys
| . + ($keys | map( {(.): $in[$k]}) | add)
else .[$k] = $in[$k]
end );
You can see how this works by running:
INDEX($mitre.records[]; .mitre_test) | refine
using an invocation of jq such as:
jq --argfile mitre in1.json -f program.jq in2.json
For the joining part of the problem, there are many relevant Q&As on SO, e.g.
How to join JSON objects on particular fields using jq?
There is probably a much more elegant way to do this, but I ended up manually walking around things and piping to new output.
Explanation:
Read in both files, pull the fields I need.
Break out the mitre_test values that were previously just a comma separated set of values with map and try.
Store the none-changing fields as a variable and then manipulate mitre_test to become an appropriately split array, removing nulls.
Group by mitre_test values, since they are the common thing that the output is based on.
Cleanup more nulls.
Sort output to look like I want it.
jq . in1.json in2.json | \
jq '.[] |{number: .number, test: .test, mitreid: .mitreid, mitre_test: .mitre_test}' |\
jq -s '[. |map(try(.mitre_test |= split(",")) // .)|\
.[] | [.number,.test,.mitreid] as $h | .mitre_test[] |$h + [.] | \
{DET_ID: .[0], tactic: .[1], techniqueID: .[2], mitre_test: .[3]}] |\
del(.[][] | nulls)' |jq '[group_by(.mitre_test)[]|{mitre_test: .[0].mitre_test, techniqueID: [.[].techniqueID],tactic: [.[].tactic], DET_ID: [.[].DET_ID]}]|\
del(.[].techniqueID[] | nulls) | del(.[].tactic[] | nulls) | del(.[].DET_ID[] | nulls)' | \
jq '.[]| [{techniqueID: .techniqueID[0],tactic: .tactic[0], metadata: [{name: "DET_ID",value: .DET_ID[]}]}] | .[] | \
select((.metadata|length)>0)'
It was a long line, so I split it among some of the basic ideas.

Omitting null values for sub() in JQ

I'm trying to change # to %23 in every context value, but I'm having problem with null values.
The shortened JSON is:
{
"stats": {
"suites": 1
},
"results": [
{
"uuid": "676-a46b-47a1-a49f-4da4e46c1120",
"title": "",
"suites": [
{
"uuid": "gghjh-56a9-4713-b139-0d5b36bc7fbc",
"title": "Login process",
"tests": [
{
"pass": false,
"fail": true,
"pending": false,
"context": "\"screenshots/login.spec.js/Login process -- should login #11 (failed).png\""
},
{
"pass": false,
"fail": false,
"pending": true,
"context": null
}
]
}
]
}
]
}
And the JQ command I think it's closest to correct is:
jq '.results[].suites[].tests[].context | strings | sub("#";"%23")'
But the problem is that I need to get in return full edited file. How could I achieve that?
You were close. To retain the original structure, you need to use the update operator (|=) instead of pipe. Enclosing the entire expression to the left of it in parentheses is also necessary, otherwise the original input will be invisible to |=.
(.results[].suites[].tests[].context | strings) |= sub("#"; "%23")
Online demo
change # to %23 in every context value
You might wish to consider:
walk( if type=="object" and (.context|type)=="string"
then .context |= gsub("#"; "%23")
else . end )

altering json value to be substring

I have a value in a json result set I would like to alter to be a substring value of the original
{
"label": "web page check",
"target": "http://www.example.com/random/page"
},
{
"label": "web page check1 ",
"target": "http://www.example1.com/random/page"
},
what I would like to do is return it as
{
"label": "web page check",
"target": "https://www.example.com"
},
{
"label": "web page check",
"target": "https://www.example1.com"
}
I have tried
jq '.[].target=(match(^https:\/\/[0-9a-zA-z.]*|^http:\/\/[0-9a-zA-z.]*).string)'
jq -c '.[] | {label: .label, target: (.target |=match(^https:\/\/[0-9a-zA-z.]*|^http:\/\/[0-9a-zA-z.]*).string})'
Using capture is often easier than using match. In your case, the following would be sufficient to modify the "target" values assuming your input is an array of objects along the lines suggested by the snippet:
map(.target |= (capture("https?(?<s>://[^/]*)") | "https" + .s))
Equivalently:
map(.target |= sub( "https?(?<s>://[^/]*).*"; "https" + .s) )
The first argument to sub (requires jq 1.5) can be any PCRE.
.[].target |= sub("(?<=com).*$"; "")

in place edit, search for nested value and then replace another value

I have an input JSON document with roughly the following form (actual data has additional keys, which should be passed through unmodified; the whitespace is adjusted for human readability, and there's no expectation that it be maintained):
{
"Rules": [
{"Filter": { "Prefix": "to_me/" }, "Status": "Enabled" },
{"Filter": { "Prefix": "from_me/" }, "Status": "Enabled" },
{"Filter": { "Prefix": "__bg/" }, "Status": "Enabled" }
]
}
I need to match .Rules[].Filter.Prefix=="to_me/" and then change the associated "Status": "Enabled" to "Disabled". Since only the first rule above has a prefix of to_me/, status of that rule would be changed to Disabled, making correct output look like the following:
{
"Rules": [
{"Filter": { "Prefix": "to_me/" }, "Status": "Disabled" },
{"Filter": { "Prefix": "from_me/" }, "Status": "Enabled" },
{"Filter": { "Prefix": "__bg/" }, "Status": "Enabled" }
]
}
I've tried several different combinations but can't seem to get it right.
Anyone have ideas?
I prefer the idiom ARRAY |= map(...) over ARRAY[] |= ..., mainly because the former can be used reliably whether or not any of the substitutions evaluate to empty:
jq '.Rules |= map(if .Filter.Prefix == "to_me/"
then .Status="Disabled" else . end)'
To overwrite the input file, you might like to consider sponge from moremutils.
Doing in-place updates can be done with |=, and deciding whether to modify content in-place can be done with if/then/else. Thus:
jq '.Rules[] |= (if .Filter.Prefix == "to_me/" then .Status="Disabled" else . end)'