PowerShell iterate through json of key value pairs - json

I have a json file that maps alpha-2 codes to country names:
{
"AF": "Afghanistan",
"EG": "Ägypten",
...
}
I want to iterate over each key value pair to process the data. How would I be able to achieve that?
I tried the following but it always returns one big object that is not iterable in a key value manner:
$json = Get-Content -Path C:\countries.json | ConvertFrom-Json

I was able to achieve it like this:
foreach ($obj in $json.PSObject.Properties) {
$obj.Name
$obj.Value
}

Related

Sorting a JSON file by outer object name

I have a json file input.json thus:
{
"foo":{
"prefix":"abc",
"body":[1,2,3]
},
"bar":{
"prefix":"def",
"body":[4,5,6]
}
}
I would like to sort it by the outer object names, with "bar" coming before "foo" in alphabetical order like so:
{
"bar":{
"prefix":"def",
"body":[4,5,6]
},
"foo":{
"prefix":"abc",
"body":[1,2,3]
}
}
to produce file output.json.
Versions of this question have been asked of Java/Javascript (here and here)
Is there a way to accomplish this using a command line tool like sed/awk or boost.json?
Using jq, you could use the keys built-in to get the key names in sorted order and form the corresponding value object
jq 'keys[] as $k | { ($k) : .[$k] }' json
Note that jq does have a field --sort-keys option, which cannot be used here, as it internally sorts the inner level objects as well.
Here's a variable-free jq solution:
to_entries | sort_by(.key) | from_entries
It is also worth noting that gojq, the Go implementation of jq, currently always sorts the keys within all JSON objects.

JQ: Convert Dictionary with List as Values to flat CSV

Original Data
I have the following JSON:
{
"foo":[
"asd",
"fgh"
],
"bar":[
"abc",
"xyz",
"ert"
],
"baz":[
"something"
]
}
Now I want to transform it to a "flat" CSV, such that for every key in my object the list in the value is expanded to n rows with n being the number of entries in the respective list.
Expected Output
foo;asd
foo;fgh
bar;abc
bar;xyz
bar;ert
baz;something
Approaches
I guess I need to use to_entries and then for each .value repeat the same .key for the first column. The jq docs state that:
Thus as functions as something of a foreach loop.
So I tried combining
to_entriesto give the keys and values from my dictionary an accessible name
then build kind of a foreach loop around the .values
and pass the result to #csv
to_entries|map(.value) as $v|what goes here?|#csv
I prepared something that at least compiles here
Don't need to use _entries function, a simple key/value lookup and string interpolation should suffice
keys_unsorted[] as $k | "\($k);\( .[$k][])"
The construct .[$k][] is an expression that first expands the value associated with each key, i.e. .foo and then with object construction, produces multiple results for each key identified and stored in $k variable.

JSON: using jq with variable keys

I have input JSON data in a bunch of files, with an IP address as one of the keys. I need to iterate over a the files, and I need to get "stuff" out of them. The IP address is different for each file, but I'd like to use a single jq command to get the data. I have tried a bunch of things, the closest I've come is this:
jq '.facts | keys | keys as $ip | .[0]' a_file
On my input in a_file of:
{
"facts": {
"1.1.1.1": {
"stuff":"value"
}
}
}
it returns the IP address, i.e. 1.1.1.1, but then how do I to go back do something like this (which is obviously wrong, but I hope you get the idea):
jq '.facts.$ip[0].stuff' a_file
In my mind I'm trying to populate a variable, and then use the value of that variable to rewind the input and scan it again.
=== EDIT ===
Note that my input was actually more like this:
{
"facts": {
"1.1.1.1": {
"stuff": {
"thing1":"value1"
}
},
"outer_thing": "outer_value"
}
}
So I got an error:
jq: error (at <stdin>:9): Cannot index string with string "stuff"
This fixed it- the question mark after .stuff:
.facts | keys_unsorted[] as $k | .[$k].stuff?
You almost got it right, but need the object value iterator construct, .[] to get the value corresponding to the key
.facts | keys_unsorted[] as $k | .[$k].stuff
This assumes that, inside facts you have one object containing the IP address as the key and you want to extract .stuff from it.
Optionally, to guard against objects that don't contain stuff inside, you could add ? as .[$k].stuff?. And also you could optionally validate keys against a valid IP regex condition and filter values only for those keys.

Replace value in specific column in CSV from hashtable

I have an imported CSV ($csv) with multiple headers, one of which is "Target Server". In the CSV this column has values device1, device2 etc.
I also have a hashtable ($hash) with name/value pairs of name(device1) - value(fqdn1) etc.
So I would like to replace the "device1" in CSV with the correct value from the hashtable, like:
foreach($row in $csv)
if($hash[$_.Name]){
$row."Target Server"= $hash[$_.Value]
}
Am I getting warm?
Use the ContainsKey() method to see if the hashtable contains an entry with a specific name/key:
foreach($row in $csv) {
if($hash.ContainsKey($row.'Target Server')) {
$row.'Target Server' = $hash[$row.'Target Server']
}
}

Parse JSON in PowerShell SELECT

I'm trying to list all my Azure VMs with their sizes using a powershell command.
Problem is that the HardwareProfile property returns a JSON object, that I would like to parse and use only the vmSize property value of that object.
So I'm running this command:
Get-AzureRmVM
Which gives me this:
ResourceGroupName : TESTRG
...
Name : ubuntu-server
...
HardwareProfile : {
"vmSize": "Standard_DS2"
}
...
NOTE the JSON in the HarwareProfile value.
What I want to do is:
Get-AzureRmVM | Select ResourceGroupName, Name, HardwareProfileText `
| Out-Gridview -PassThru
Which works - only, I would like to get rid of the JSON notation in the HardwareProfileText. Using Format-Table looks like so:
ResourceGroupName Name HardwareProfileText
----------------- ---- -------------------
TESTRG ubuntu-server {...
So the question is: how can I get only the value of vmSize in this table ? Can I sneak ConvertFrom-Json in somewhere?
Can't you use the select-expression directly and convert the json-string into an object? So you can use it later in your pipeline.
Something like :
select #{Name="VMSize";Expression={($_|ConvertFrom-Json).vmSize}};
Given your json as a text in a file (for a simple-test):
(Get-Content -raw C:\tmp\test.json)|select #{Name="VMSize";Expression={($_|ConvertFrom-Json).vmSize}};
This will give you a property with only the VmSize. You can combine the expression select with normal properties as well, or multiple expressions and then continue to pass it down the pipeline if you want to filter on additional criteria.
I don't know the get-azureRmVm function but it works just fine with the property InstanceSize instead of HardwareProfileText.
Import-Module 'Azure'
Get-AzureVM | Select ResourceGroupName, Name, InstanceSize `
| Out-Gridview -PassThru
Result