I'm trying to pack my data into objects before displaying them with ConvertTo-Json. The test case below shows perfectly how I'm dealing with data and what problem occurs:
$array = #("a","b","c")
$data = #{"sub" = #{"sub-sub" = $array}}
$output = #{"root" = $data}
ConvertTo-Json -InputObject $data
ConvertTo-Json -InputObject $output
Output (formatted by hand for clarity):
{ "sub": { "sub-sub": [ "a", "b", "c" ] }}
{ "root": { "sub": { "sub-sub": "a b c" } }}
Is there any way to assign $data to $output without this weird implicit casting?
As mentioned in the comments, ConvertTo-Json will try to flatten the object structure beyond a maximum nesting level, or depth, by converting whatever object it finds beyond that depth to a string.
The default depth is 2, but you can specify that it should go deeper with the Depth parameter:
PS C:\> #{root=#{level1=#{level2=#("level3-1","level3-2")}}}|ConvertTo-Json
{
"root": {
"level1": {
"level2": "level3-1 level3-2"
}
}
}
PS C:\> #{root=#{level1=#{level2=#("level3-1","level3-2")}}}|ConvertTo-Json -Depth 3
{
"root": {
"level1": {
"level2": [
"level3-1",
"level3-2"
]
}
}
}
Related
i have an array converted to a json ($user_data_JSON = $user_data | ConvertTo-Json) in powershell which looks like this
$user_data_JSON
{
[
{
"targetId": "5007Y00000K5nkjQAB",
"login": "login1",
"password": "scRHDztkKbO"
},
{
"targetId": "5007Y00000MNbDvQAL",
"login": "login2",
"password": "scRHDztkKbO"
}
]
}
But i need to modify it like this with a KEY value on top:
$user_data_JSON
{
"logins":[
{
"targetId": "5007Y00000K5nkjQAB",
"login": "login1",
"password": "scRHDztkKbO"
},
{
"targetId": "5007Y00000MNbDvQAL",
"login": "login2",
"password": "scRHDztkKbO"
}
]
}
How can I manage to achieve this?
I've already tried to create a new array object, add the key and convert it to a json file like this:
$jsonBase = #{}
$list = New-Object System.Collections.ArrayList
$list.Add("Foo")
$list.Add("Bar")
$jsonBase.Add("Data",$list)
$jsonBase | ConvertTo-Json
To get something like this:
{
"Data": [
"Foo",
"Bar"
]
}
but when I convert my array to a json again, it looks kind of trunkated:
$jsonBase | ConvertTo-Json -Depth 10
{
"logins": [
"[\r\n {\r\n \"targetId\": \"5007Y00000K5nkjQAB\",\r\n \"login\": \"login1\",\r\n \"password\": \"scRHDztkKbO\"\r\n },\r\n {\r\n
\"targetId\": \"5007Y00000MNbDvQAL\",\r\n \"login\": \"login2\",\r\n \"password\": \"scRHDztkKbO\"\r\n },"
]
}
How can I get a propoer JSON object?
Thanks
Don't use the JSON (text) representation of your array ($user_data_JSON), use its original, object form ($user_data) to construct a wrapper object with the desired top-level property:
[pscustomobject] #{ logins = $user_data } | ConvertTo-Json -Depth 10
As for what you tried:
If you use a preexisting JSON string as the value of property to be converted to JSON with ConvertTo-Json, it is treated like any other string value, resulting in the representation you saw, with " characters escaped as \" and (CRLF) newlines as \r\n
A simple example:
[pscustomobject] #{
foo = '{ "bar":
"baz" }'
} | ConvertTo-Json
Output (note how the foo property value became a single-line string with " and newlines escaped):
{
"foo": "{ \"bar\": \n \"baz\" }"
}
Working with Graph API and Intune. I create hash table in my script and covert it to JSON which POST to GraphAPI.
But in one case during conversion I lose array. Because of this GraphAPI does not want to accept JSON and returns error.
Case when conversion is correct:
$TargetGroupIDs = #('111', '222')
$AppAssignment = #{
mobileAppAssignments = #{
}
}
$AppAssignment.mobileAppAssignments = foreach ($GroupID in $TargetGroupIDs) {
$Hash = [ordered]#{
"#odata.type" = "#microsoft.graph.mobileAppAssignment"
intent = "Required"
settings = $null
target = #{
"#odata.type" = "#microsoft.graph.groupAssignmentTarget"
groupId = "$GroupID"
}
}
write-output (New-Object -Typename PSObject -Property $hash)
}
$AppAssignment | ConvertTo-Json -Depth 50
Output:
{
"mobileAppAssignments": [
{
"#odata.type": "#microsoft.graph.mobileAppAssignment",
"intent": "Required",
"settings": null,
"target": {
"groupId": "111",
"#odata.type": "#microsoft.graph.groupAssignmentTarget"
}
},
{
"#odata.type": "#microsoft.graph.mobileAppAssignment",
"intent": "Required",
"settings": null,
"target": {
"groupId": "222",
"#odata.type": "#microsoft.graph.groupAssignmentTarget"
}
}
]
}
But when I have one element in $TargetGroupIDs conversion is not correct:
$TargetGroupIDs = #('111')
$AppAssignment = #{
mobileAppAssignments = #{
}
}
$AppAssignment.mobileAppAssignments = foreach ($GroupID in $TargetGroupIDs) {
$Hash = [ordered]#{
"#odata.type" = "#microsoft.graph.mobileAppAssignment"
intent = "Required"
settings = $null
target = #{
"#odata.type" = "#microsoft.graph.groupAssignmentTarget"
groupId = "$GroupID"
}
}
write-output (New-Object -Typename PSObject -Property $hash)
}
$AppAssignment | ConvertTo-Json -Depth 50
Output:
{
"mobileAppAssignments": {
"#odata.type": "#microsoft.graph.mobileAppAssignment",
"intent": "Required",
"settings": null,
"target": {
"groupId": "111",
"#odata.type": "#microsoft.graph.groupAssignmentTarget"
}
}
}
Please note difference in brackets after mobileAppAssignments. In first case [], but in second case {}.
Could someone tell what I am missing in second case?
Theo and Santiago Squarzon have provided the crucial hint in the comments, but let me spell it out:
To ensure that the output from your foreach statement is an array, enclose it in #(), the array-subexpression operator:
$AppAssignment.mobileAppAssignments = #(
foreach ($GroupID in $TargetGroupIDs) {
[pscustomobject] #{
"#odata.type" = "#microsoft.graph.mobileAppAssignment"
intent = "Required"
settings = $null
target = #{
"#odata.type" = "#microsoft.graph.groupAssignmentTarget"
groupId = "$GroupID"
}
}
}
)
Also note that simplified syntax custom-object literal syntax ([pscustomobject] #{ ... }).
#(...) ensures that an array (of type [object[]]) is returned, irrespective of how many objects, if any, the foreach statement outputs.
Without #(...), the data type of the collected output depends on the number of output objects produced by a statement or command:
If there is no output object, the special "AutomationNull" value is returned, which signifies the absence of output; this special value behaves like an empty collection in enumeration contexts, notably the pipeline, and like $null in expression contexts - see this answer for more information; in the context at hand, it would be converted to a null JSON value.
If there is one output object, it is collected as-is, as itself - this is what you saw.
Only with two or more output objects do you get an array (of type [object[]]).
Note: The behavior above follows from the streaming behavior of the PowerShell pipeline / its success output stream: object are emitted one by one, and needing to deal with multiple objects only comes into play if there's a need to collect output objects: see this answer for more information.
For context, I am attempting to create a cmdlet that would allow for single value substitutions on arbitrary Json files, for use in some pipelines. I've managed to get this working for non-array-containing Json.
A representative bit of Json:
{"test": {
"env": "dev",
"concept": "abstraction",
"array": [
{"id":1, "name":"first"},
{"id":2, "name":"second"}
]
}
}
I want to be able to replace values by providing a function with a path like test.array[1].name and a value.
After using ConvertFrom-Json on the Json above, I attempt to use the following function (based on this answer) to replace second with third:
function SetValue($object, $key, $value) {
$p1, $p2 = $key.Split(".")
$a = $p1 | Select-String -Pattern '\[(\d{1,3})\]'
if ($a.Matches.Success) {
$index = $a.Matches.Groups[1].Value
$p1 = ($p1 | Select-String -Pattern '(\w*)\[').Matches.Groups[1].Value
if ($p2.length -gt 0) { SetValue -object $object.$p1[$index] -key $p2 -value $value }
else { $object.$p1[$index] = $value }
}
else {
if ($p2.length -gt 0) { SetValue -object $object.$p1 -key $p2 -value $value }
else {
Write-Host $object.$p1
$object.$p1 = $value
}
}
}
$content = SetValue -object $content -key "test.array[1].name" -rep "third"
Unfortunately this results in the following:
{ "test": {
"env": "dev",
"concept": "abstraction",
"array": [
"#{id=1; name=first}",
"#{id=2; name=third}"
]
}
}
If the values in the array aren't objects the code works fine as presented, it's only when we get to objects within arrays that this output happens.
What would be a way to ensure that the returned Json contains an array that is more in line with the input?
Edit: please note that the actual cause of the issue lay in not setting the -Depth property of ConvertTo-Json to 3 or greater. Doing so restored the resulting Json to the expected format. The accepted answer was still helpful in investigating the cause.
While Invoke-Expression (iex) should generally be avoided, there are exceptional cases where it offers the simplest solution.
$fromJson = #'
{
"test": {
"env": "dev",
"concept": "abstraction",
"array": [
{"id":1, "name":"first"},
{"id":2, "name":"second"}
]
}
}
'# | ConvertFrom-Json
$nestedPropertyAccessor = 'test.array[1].name'
$newValue = 'third'
Invoke-Expression "`$fromJson.$nestedPropertyAccessor = `"$newValue`""
Important:
Be sure that you either fully control or implicitly trust the content of the $nestedPropertyAccessor and $newValue variables, to prevent inadvertent or malicious execution of injected commands.
On re-conversion to JSON, be sure to pass a high-enough -Depth argument to ConvertTo-Json; with the sample JSON, at least -Depth 3 is required - see this post.
I'm trying to pack my data into objects before displaying them with ConvertTo-Json. The test case below shows perfectly how I'm dealing with data and what problem occurs:
$array = #("a","b","c")
$data = #{"sub" = #{"sub-sub" = $array}}
$output = #{"root" = $data}
ConvertTo-Json -InputObject $data
ConvertTo-Json -InputObject $output
Output (formatted by hand for clarity):
{ "sub": { "sub-sub": [ "a", "b", "c" ] }}
{ "root": { "sub": { "sub-sub": "a b c" } }}
Is there any way to assign $data to $output without this weird implicit casting?
As mentioned in the comments, ConvertTo-Json will try to flatten the object structure beyond a maximum nesting level, or depth, by converting whatever object it finds beyond that depth to a string.
The default depth is 2, but you can specify that it should go deeper with the Depth parameter:
PS C:\> #{root=#{level1=#{level2=#("level3-1","level3-2")}}}|ConvertTo-Json
{
"root": {
"level1": {
"level2": "level3-1 level3-2"
}
}
}
PS C:\> #{root=#{level1=#{level2=#("level3-1","level3-2")}}}|ConvertTo-Json -Depth 3
{
"root": {
"level1": {
"level2": [
"level3-1",
"level3-2"
]
}
}
}
Given that I am processing the following $json variable, how would I grab each server id (i.e. 215d1109-216d-48c3-af8e-998bb9bc3ca0 and 440cf918-3ee0-4143-b289-f63e1d2000e6 in this case) and put it into an array?
Right now $obj.servers.id returns nothing (as expected), but $obj.servers.id[0] returns an error message.
clear
[System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions") | out-null
$json = '
{
"servers": [
{
"admin_password": "qpYU66rKxmnK",
"id": "215d1109-216d-48c3-af8e-998bb9bc3ca0",
"links": [
{
"href": "http://openstack.example.com/v3/servers/<id>",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/<id>",
"rel": "bookmark"
}
]
},
{
"admin_password": "wfksH3GTTseP",
"id": "440cf918-3ee0-4143-b289-f63e1d2000e6",
"links": [
{
"href": "http://openstack.example.com/v3/servers/<id>",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/<id>",
"rel": "bookmark"
}
]
}
]
}
'
$ser = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$obj = $ser.DeserializeObject($json)
$obj.servers.id;
Please note that I am using Powershell 2.0.
You're misunderstanding the structure of your data. $obj.servers is an array with two fields, each of which contains a dictionary, not an object containing an array id. You need to make the index access like this:
$obj.servers[0].id
$obj.servers.id[0] will certainly throw an error, because the array object $obj.servers doesn't have a property id, so in PowerShell v2 $obj.servers.id returns $null, and index access on a null array fails (as you'd expect).
Demonstration:
PS C:\> [void][Reflection.Assembly]::LoadWithPartialName('System.Web.Extensions')
$json = #'
...
'#
PS C:\> $ser = New-Object Web.Script.Serialization.JavaScriptSerializer
PS C:\> $obj = $ser.DeserializeObject($json)
PS C:\> $obj.servers.GetType().FullName
System.Object[]
PS C:\> $obj.servers.id -eq $null
True
PS C:\> $obj.servers[0].GetType().Name
Dictionary`2
PS C:\> $obj.servers[0] | Format-Table -AutoSize
Key Value
--- -----
admin_password qpYU66rKxmnK
id 215d1109-216d-48c3-af8e-998bb9bc3ca0
links {System.Collections.Generic.Dictionary`2[System.Str...
PS C:\> $obj.servers[0].id
215d1109-216d-48c3-af8e-998bb9bc3ca0
To extract all IDs simply pipe the array into a ForEach-Object loop where you echo the id property:
PS C:\> $ids = $obj.servers | % { $_.id }
PS C:\> $ids
215d1109-216d-48c3-af8e-998bb9bc3ca0
440cf918-3ee0-4143-b289-f63e1d2000e6