Yet another loop JSON object using Powershell - json

I am not able to twist my head into understanding how to get Powershell to loop the entire JSON Structure, it wont' loop the System.Object[]
$x = ConvertFrom-Json '{
"Car companies": {
"Name of Company": "Ford",
"Cars": [{
"Name of car": "Ranger",
"Config": "Pickup"
},
{
"Name of car": "Puma",
"Config": "Hatchback"
}]
}
}'
foreach( $rootProperty in #($x.psobject.properties | where-object {$_.MemberType -eq "NoteProperty"}) ) {
write-host " - '$($rootProperty.Name)' = '$($rootProperty.Value)'"
foreach( $childProperty in #($rootProperty.Value.psobject.properties ) ) {
write-host "'$($childProperty.Name)' = '$($childProperty.Value)'"
}
}
Outut I get now is just
- 'Brand' = '#{Name of Brand=Ford; Cars=System.Object[]}'
Name of Brand' = 'Ford'
Cars' = ' '
...as a follop How to iterate through a unknown JSON data/object?

tl;dr
You're seeing a bug that unexpectedly string-expands the Cars property value's array elements to the empty string.
A simple workaround - for display purposes only - is to pipe the property value to Out-String to get the usual display representation:
"'$($childProperty.Name)' = '$($childProperty.Value | Out-String)'"
You're seeing a bug in how arrays of [pscustomobject] instances are stringified (as of PowerShell Core 7.0.0-preview.6):
Generally, PowerShell arrays are stringified by joining the stringified element representations with the separator specified in the $OFS preference variable, which defaults to a space char.
Normally, [pscustomobject] instances have a string representation that resembles a hashtable literal (but isn't one); e.g.:
PS> $custObj = [pscustomobject] #{ foo = 'bar' }; "$custObj"
#{foo=bar} # string representation that *resembles* a hashtable literal
Unexpectedly - and this is the bug - when custom objects are the elements of an array, they stringify to the empty string, which is what you saw:
PS> $custObj = [pscustomobject] #{ foo = 'bar' }; $arr = $custObj, $custObj; "[$arr]"
[ ] # !! Bug: custom objects stringified to empty strings, joined with a space
This is an indirect manifestation of a long-standing bug reported in this GitHub issue: that is, elements of an array being stringified are stringified by calls to their .ToString() method, and calling .ToString() on custom objects unexpectedly yields the empty string (unlike the string representation you get when you directly reference a single custom object in an expandable string, as shown above).

Related

ConvertTo-JSON from string - access JSON fields by name in PowerShell

How can I access a field like $body.uuid?
This is what I have tried:
$body = #"
{ "uuid": "Test07",
"subject": "Template07-Subject",
}
"#
$bodyJSON = ConvertTo-Json $body
Write-Host $bodyJSON
Write-Host "uuid=$($bodyJSON.uuid)"
Write-Host "uuid=$($bodyJSON.$uuid)"
Results:
"{ \"uuid\": \"Test07\",\r\n \"subject\": \"Template07-Subject\",\r\n}"
uuid=
uuid=
Your $body variable contains a JSON string.
Unless your intent is to embed that string in another JSON string, do not call ConvertTo-Json on it - the latter's purpose is to convert objects to JSON strings.
In order to parse the JSON string into an object (graph), pass it to ConvertFrom-Json, which returns [pscustomobject] instance(s).
You can use regular property access, such as .uuid on the resulting object(s).
Note:
As bluuf points out, your original JSON contains an extraneous , after the "subject" property, which makes it technically malformed - this has been corrected below.
Note, however, that ConvertTo-Json in PowerShell (Core) 7+ still accepts such JSON, whereas Windows PowerShell does not.
# Create a string containing JSON
$body = #"
{
"uuid": "Test07",
"subject": "Template07-Subject"
}
"#
# Parse the JSON string into a PowerShell object (graph).
$bodyAsObject = ConvertFrom-Json $body
# Now you can use property access.
$bodyAsObject.uuid # -> 'Test07'

PowerShell JSON string escape (backslash)

I need to HttpPost a Json-body to a ASP.NET Core Web Api endpoint (controller) using a PowerShell script.
$CurrentWindowsIdentity = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$CurrentPrincipalName = $CurrentWindowsIdentity.Identity.Name
# Build JSON payload
$JsonString = #"
{
"CurrentPrincipalName":"$CurrentPrincipalName"
}
"#
$response = Invoke-RestMethod -Uri "https://webapiendpoint.tld/api/somecontroller" -Method Post -Body $JsonString -ContentType "application/json"
Since the value of the variable $CurrentPrincipalName can be domain\username, the json get's invalid because of the backslash, which is not properly escaped.
Error in the log of the web api:
JSON input formatter threw an exception: 'C' is an invalid escapable character within a JSON string. The string should be correctly escaped. Path: $.CurrentPrincipalName | LineNumber: 15 | BytePositionInLine: 36.
System.Text.Json.JsonException: 'C' is an invalid escapable character within a JSON string. The string should be correctly escaped. Path: $.CurrentPrincipalName
How can i make sure that when creating a json string and adding variables - whose values of course can not be controlled - the json string gets properly escaped?
i also tried ConvertTo-Json like:
$JsonConverted = $JsonString | ConvertTo-Json
and then HttpPost that object, but that was even worse:
JSON input formatter threw an exception: The JSON value could not be converted to solutionname.model. Path: $ | LineNumber: 0 | BytePositionInLine: 758.
The robust way to create JSON text is to construct your data as a hash table (#{ ... }) or custom object ( [pscustomobject] #{ ... }) first and pipe to ConvertTo-Json:
$JsonString = #{
CurrentPrincipalName = $CurrentPrincipalName
} | ConvertTo-Json
That way, PowerShell performs any necessary escaping of values for you, notably including doubling the literal \ character in your $CurrentPrincipalName value to ensure that it is treated as a literal.
Note:
Depending on how deeply nested the hashtable is, you may have to add a -Depth argument to the ConvertTo-Json call to prevent more data from getting truncated - see this post for more information.
If you have multiple properties and want to preserve their definition order in the JSON representation, use an ordered hash table ([ordered] #{ ... }) or a custom object.

Inputting JSON data in Powershell

Currently, I'm attempting to call upon an API to run a POST, with JSON data as the body. So I was wondering if anyone would be able to tell me how I need to format the text below inside the variable $postParams. I'm pretty new at working with JSON so I'm having so trouble with this.
Currently, I only have the following and don't know what to do about the second line on.
$postParams = #{name='Example'}
Here's is the entire data I was hoping to add to $postParams. So if you could help me with the 2nd, 4th, and 8th that'd be awesome. Thanks!
{
"name":"Example",
"template":{"name":"Template"},
"url":"http://localhost",
"page":{"name":"Landing Page"},
"smtp":{"name":"Sending Profile"},
"launch_date":"2019-10-08T17:20:00+00:00",
"send_by_date":null,
"groups":[{"name":"test group"}]
}
You'll need a here-string and ConvertFrom-Json.
here-string:
Quotation marks are also used to create a here-string. A here-string is a single-quoted or double-quoted string in which quotation marks are interpreted literally. A here-string can span multiple lines. All the lines in a here-string are interpreted as strings, even though they are not enclosed in quotation marks.
The resulting code:
# Use a PowerShell here string to take JSON as it is
$jsonString = #"
{
"name":"Example",
"template":{"name":"Template"},
"url":"http://localhost",
"page":{"name":"Landing Page"},
"smtp":{"name":"Sending Profile"},
"launch_date":"2019-10-08T17:20:00+00:00",
"send_by_date":null,
"groups":[{"name":"test group"}]
}
"#
# Pipe the string to create a new JSON object
$jsonObject = $jsonString | ConvertFrom-Json
# The resulting JSON object has properties matching the properties in the orig. JSON
$jsonObject.name
$jsonObject.url
# Nested property
$jsonObject.template.name
# Nested property in array
$jsonObject.groups[0].name
I've posted an online version of the above code at tio.run, so you can play around with it.
If you want to update several properties of the $jsonObject you can do the following:
$jsonObject.name = "NEW NAME"
$jsonObject.url = "NEW URL"
$jsonObject | ConvertTo-Json
ConvertTo-Json will take your object and create an appropriate JSON string:
{
"name": "NEW NAME",
"template": {
"name": "Template"
},
"url": "NEW URL",
"page": {
"name": "Landing Page"
},
"smtp": {
"name": "Sending Profile"
},
"launch_date": "2019-10-08T17:20:00+00:00",
"send_by_date": null,
"groups": [
{
"name": "test group"
}
]
}
If you $jsonObject has more than two levels of depth, use the -Depth parameter, otherwise not all object information will be included in the JSON string.
ConvertTo-Json:
-Depth
Specifies how many levels of contained objects are included in the JSON representation. The default value is 2.
Here is a tio.run link to a ConvertTo-Json example.
Hope that helps.
I can't test it currently, but try this.
$postParams = #'
{
"name":"Example",
"template":{"name":"Template"},
"url":"http://localhost",
"page":{"name":"Landing Page"},
"smtp":{"name":"Sending Profile"},
"launch_date":"2019-10-08T17:20:00+00:00",
"send_by_date":null,
"groups":[{"name":"test group"}]
}
'#
Make a hashtable, then convert to JSON:
$Hashtable = #{
Key1 = "Value1"
Key2 = "Value2"
}
$Json = $Hashtable | ConvertTo-Json

Add windows env variables to json object using powershell

I'm quite new to powershell and just need it for a small task so please excuse my complete and utter ineptitude for the language. I was wondering if it were possible to form a json object based off environment variables and a variable that has already been declared earlier in my script. The variable that was already declared is based off a json config named optionsConfig.json and the contents of that file are here.
{"test1": ["options_size", "options_connection", "options_object"],
"test2":["options_customArgs", "options_noUDP", "options_noName"]}
The purpose of the $Options variable in the code below is to take each element in the list value for the respective test and assume that those elements are environment variables in the system, then find their values and form a dictionary object that will be used in the json.
Here is what I have so far.
# Read the JSON file into a custom object.
$configObj = Get-Content -Raw optionsConfig.json |
ConvertFrom-Json
# Retrieve the environment variables whose
# names are listed in the $env:test property
# as name-value pairs.
Get-Item -Path env:* -Include $configObj.$env:testTool
$Options = Get-Item -Path env:* -Include $configObj.$env:testTool |
% {$hash = #{}} {$hash[$_.Name]=$_.Value} {$hash}
The $Options variable looks like so when converted to json
{
"options_size": "default",
"options_object": "forward open",
"options_connection": "connected"
}
I have a few other environment variable values that I would like to be a part of the json object. Those 3 other environment variables I would like the value of are listed below.
$Env.testTool = "test1"
$Env.RecordName = "Record1"
$Env.Target = "Target1"
How would I construct a powershell statement to get the json object to be formatted like this? -
data = {"test": $Env.testTool, "target": "$Env.Target",
"options": "$Options", "RecordName': "$Env.RecordName"}
The keys are all predefined strings and $Options is the dict object from up above. Am I able to form a Json object like this in powershell and how would it be done? Any help would be appreciated. This appears to be the last step in my struggle with powershell.
Here is what I have done.
$jObj = [ordered]#{test= $Env:testTool}
When I change this variable to $jObj = [ordered]#{test= $Env:testTool,options= $Options} I get an error saying missing expression after ','
When I change this variable to $jObj = [ordered]#{test= $Env:testTool,options= $Options} I get an error saying missing expression after ','
Entries of a hashtable literal (#{ ... } or, in its ordered form, [ordered] #{ ... }) must be separated:
either by newlines (each entry on its own line)
or by ; if placed on the same line.
Thus, the following literals are equivalent:
# Multiline form
#{
test= $env:testTool
RecordName= $env:RecordName
Target= $env.Target
options=$Options
}
# Single-line form; separator is ";"
#{ test= $env:testTool; RecordName= $env:RecordName; Target= $env.Target; options=$Options }
Get-Help about_Hashtables has more information.
$jObj = #{test= $env:testTool
RecordName= $env:RecordName
Target= $env.Target
options=$Options}
$jObj | ConvertTo-Json | Set-Content jsonStuff.json
JsonStuff.json is the new json file for the new json object. This syntax for forming $jObj seems to have done the trick.

PowerShell -- Accessing a JArray inside a JObject

I have a Json object
{
"ProjectDirectory": "C:\\Main",
"SiteName": "RemoteOrder",
"ParentPath": "/Areas//Views",
"VirtualDirectories": [
{
"Name": "Alerts",
"Path": "\\Areas\\RemoteOrder\\Views\\Alerts"
},
{
"Name": "Analytics",
"Path": "\\Areas\\RemoteOrder\\Views\\Analytics"
},
{
"Name": "Auth",
"Path": "\\Areas\\RemoteOrder\\Views\\Auth"
}
]
}
that I created by
$config = [Newtonsoft.Json.Linq.JObject]::Parse($file)
I can access things like
$config["ProjectDirectory"]
$config["VirtualDirectories"]
But I can not get to the element inside the VirtualDirectories JArray
I confirmed
$config["VirtualDirectories"][0].GetType() // JObject
$config["VirtualDirectories"].GetType() // JArray
$config // JObject
I have tried
$config["VirtualDirectories"][0]["Name"]
$config["VirtualDirectories"][0]["Path"]
$config["VirtualDirectories"][0][0]
$config["VirtualDirectories"][0].GetValue("Name")
When I try
$config["VirtualDirectories"][0].ToString()
I get
{
"Name": "Alerts",
"Path": "\\Areas\\RemoteOrder\\Views\\Alerts"
}
What I am really trying to do is access it a loop but again I can not seem to access the JObject Elements
You are close. $config["VirtualDirectories"][0]["Name"] will give you a JValue containing the text. You just need to use the Value property from there to get the actual string. Here is how you would do it in a ForEach loop:
$config = [Newtonsoft.Json.Linq.JObject]::Parse($file)
ForEach ($dir in $config["VirtualDirectories"])
{
$name = $dir["Name"].Value
$path = $dir["Path"].Value
...
}
To complement Brian Rogers' helpful answer:
As a more convenient alternative to index syntax (["<name>"]) you can use property syntax
(.<name>), because the JObject instances returned have dynamic properties named for their keys:
$config = [Newtonsoft.Json.Linq.JObject]::Parse($file)
foreach ($dir in $config.VirtualDirectories) {
$name = $dir.Name.Value # as in Brian's answer: note the need for .Value
$path = $dir.Path.Value # ditto
# Sample output
"$name=$path" # outputs 'Alerts=\Areas\RemoteOrder\Views\Alerts', ...
}
I presume that the reason you chose to work with Json.NET types directly is performance compared to PowerShell's built-in ConvertFrom-Json cmdlet.
As an aside: There is a PowerShell wrapper for Json.NET that you can install with Install-Module -Scope CurrentUser newtonsoft.json , for instance, which implicitly gives you access to the [Newtonsoft.Json.Linq.JObject] type. However, this wrapper - which represents objects as ordered hashtables - is even slower than ConvertFrom-Json.
Aside from performance, the following limitations of ConvertFrom-Json may make it necessary to use a third-party library such as Json.Net anyway:
Empty-string keys are not supported.
Keys that differ in case only (e.g., foo vs. Foo) are not supported.
For contrast, here's the equivalent - but generally slower - ConvertFrom-Json solution:
ConvertFrom-Json represents the JSON objects as [pscustomobject] instances whose properties are named for the keys, allowing for a more natural syntax without the need to access .Value:
$config = ConvertFrom-Json $json
foreach ($dir in $config.VirtualDirectories) {
$name = $dir.Name # no .Value needed
$path = $dir.Path # ditto
# Sample output
"$name=$path" # outputs 'Alerts=\Areas\RemoteOrder\Views\Alerts', ...
}