How can I query change value of the "type" string in my JSON file with PowerShell? I can't get to the "type" string.
JSON file
{
"name": "b",
"compatibilityLevel": 1400,
"model": {
"culture": "c",
"dataSources":[
{
"type": "structured"
}
]
}}
PowerShell
$pathToJson = "C:\Model.bim"
$a = Get-Content $pathToJson | ConvertFrom-Json
$a.'model.dataSources.type' = "c"
$a | ConvertTo-Json -Depth 10 | Set-Content $pathToJson
tl;dr
$a.model.dataSources[0].type = 'c'
Note the need to specify index [0], because $a.model.dataSources is an array.
AS for what you tried:
$a.'model.dataSources.type' = "c"
You cannot use a property path stored in a string ('...') to directly access a nested property, because PowerShell interprets 'model.dataSources.type' as the name of a single property.
See this answer for workarounds.
Even with that problem corrected, $a.model.dataSources.type = "c" does not work, because $a.model.dataSources returns an array of values, and you cannot directly set a property on that array's elements; instead you must explicitly target the array element of interest, as shown above ([0]).
Note you can get the array elements' .type values with $a.model.dataSources.type, via a PSv+ feature called member-access enumeration, but that doesn't work on setting - by design, to prevent possibly inadvertent updating of all array elements with the same value; see this answer.
Related
I have a json file, simplified version of it looks like this:
{
"Location": "EU",
"Country": {
"City": "xxx",
"Town": "xxx"
},
"Transport": {
"Train": "xxx"
}
}
I have run the ConvertFrom-Json command to convert to PSObject:
$conversion = Get-Content $path | ConvertFrom-Json
This will give me an output like this:
Location : EU
Country : #{City="xxx"; Town="xxx"}
Transport : #{Train="xxx"}
Question
How can I get the nested values to print out separately? I would want them all to print out like the "Location:EU" one
Is there a different command to ConvertFrom-Json that i should be using for this? Or do I just need to mess around with ConvertFrom-Json command a bit?
To note:
I am not just looking for a pretty print out - I would need them all separately for a script I am writing that will be looping through all the key/value pairs
I have read about the -Depth flag when using ConvertFrom-Json and does not seem to fix anything here - it seemed this was more relevant for ConvertTo-Json
In order to report all leaf properties as name-value pairs (i.e. those properties that contain primitive JSON values as opposed to containing nested objects with properties and / or arrays), you need to recursively walk the object graph:
Find helper function Get-LeafProperty below; assuming you have already defined it, you can call it as follows:
#'
{
"Location": "EU",
"Country": {
"City": "xxx",
"Town": "xxy"
},
"Transport": {
"Train": "xxz"
}
}
'# |
ConvertFrom-Json |
Get-LeafProperty
Output (the display formatting of [pscustomobject] instances with .Name and .Value properties representing all the leaf properties):
Name Value
---- -----
Location EU
City xxx
Town xxy
Train xxz
Get-LeafProperty source code:
# Walks a potentially nested [pscustomobject] graph
# as returned by ConvertFrom-Json and outputs all
# leaf properties as name-value custom objects.
function Get-LeafProperty {
param([Parameter(ValueFromPipeline)] [object] $InputObject)
process {
if ($InputObject -is [array]) { # array as single input object -> recurse
foreach ($o in $InputObject) { Get-LeafProperty $o }
}
else {
# Assumed to be a (potentially nested) [pscustomobject] instance:
# Recursively process its properties.
foreach ($p in $InputObject.psobject.properties) {
if ($p.Value -is [array]) { # array -> recurse
foreach ($o in $p.Value) { Get-LeafProperty $o }
} elseif ($p.Value -is [System.Management.Automation.PSCustomObject] ) { # nested [pscustomobject] -> recurse
Get-LeafProperty $p.Value
} else { # leaf property reached -> output name-value pair
[pscustomobject] #{ Name = $p.Name; Value = $p.Value }
}
}
}
}
}
Note: A variant of this function that outputs property name paths (e.g. Country.City) instead of just their names (e.g. City) can be found in this answer.
I have one array of hashtables like the one below:
$hashtable1 = #{}
$hashtable1.name = "aaa"
$hashtable1.surname =#()
$hashtable1.surname += "bbb"
$hashtable2 = #{}
$hashtable2.name = "aaa"
$hashtable2.surname =#()
$hashtable2.surname += "ccc"
$hashtable3 = #{}
$hashtable3.name = "bbb"
$hashtable3.surname = #()
$hashtable3.surname += "xxx"
$A = #($hashtable1; $hashtable2; $hashtable3)
I need to iterate though the array and I need to find out duplicates based on hashtable[].name
Then I need to group those hashtable.surname to hashtable[].surname so that the result will be an array of hashtables that will group all for name all the surnames:
$hashtable1.name = "aaa"
$hashtable1.surname = ("bbb","ccc")
$hashtable3.name = "bbb"
$hashtable3.surname = ("xxx")
I was looking into iterating to empty array
+
I have found this link:
powershell compare 2 arrays output if match
but I am not sure on how to reach into the elements of the hashtable.
My options:
I was wondering if -contain can do it.
I have read about compare-object but I am not sure it can be done like that.
(It looks a bit scary in the moment)
I am on PS5.
Thanks for your help,
Aster
You can group your array items by the names using a scriptblock like so.
Once grouped, you can easily build your output to do what you seek.
#In PS 7.0+ you can use Name directly but earlier version requires the use of the scriptblock when dealing with arrays of hashtables.
$Output = $A | Group-Object -Property {$_.Name} | % {
[PSCustomObject]#{
Name = $_.Name
Surname = $_.Group.Surname | Sort-Object -Unique
}
}
Here is the output variable content.
Name Surname
---- -------
aaa {bbb, ccc}
bbb xxx
Note
Improvements have been made in PS 7.0 that allows you to use simply the property name (eg: Name) in Group-Object for arrays of hashtables, just like you would do for any other arrays type. For earlier version though, these particular arrays must be accessed by passing the property in a scriptblock, like so: {$_.Name}
References
MSDN - Group_Object
SS64 - Group Object
Dr Scripto - Use a Script block to create custom groupings in PowerShell
This question already has answers here:
Unexpected ConvertTo-Json results? Answer: it has a default -Depth of 2
(2 answers)
Closed 2 years ago.
This question has also been answered here:
Stackoverflow: Unexpected ConvertTo-Json results? Answer: it has a default -Depth of 2
GitHub: ConvertFrom-Json and ConvertTo-Json breaks arrays
Mircrosoft Docs: ConvertTo-JSON
TL;DR
If you save your .json with ConvertTo-JSON and it break it, you may want to speccify the -Depth parameter, as it's default value it 2 and it wont parse nestet objects beyond that.
Original Post:
So I want to load, then save data from a JSON file through a PowerShell skript. The JSON contains an array within an object, which looks like this:
{
"head": {
"head2": {
"data0": "a",
"data1": "b"
},
"head3": {
"data8": [
"x",
"y",
"z"
],
"data9": "hello"
}
}
}
Notice the array "data8".
Now when I load and save the file like so: Get-Content test.json | ConvertFrom-JSON | ConvertTo-JSON I want to end up with the exact same file, since I'm not changing anything. However the result is this:
{
"head": {
"head2": {
"data0": "a",
"data1": "b"
},
"head3": {
"data8": "x y z",
"data9": "hello"
}
}
}
The "data8" has been reduced to a single string and I struggle to find out why. It seems to happen during the ConvertTo-JSON because when I don't do that yet, It will give me an array of strings containing x,y and z.
E.g. (Get-Content test.json | ConvertFrom-JSON).head.head3.data8 will result in
x
y
z
and (Get-Content test.json | ConvertFrom-JSON).head.head3.data8 -is [array] gives True
Things I've tried:(get-content test.json | convertfrom-json).head.head3.data8
Various encoding methods in test.json, but this even happens when I save the file as utf8 and add -encoding utf8 to Get-Content so I don't believe that is has to do with that.
For the sake of trying, I even added -compress to ConvertTo-JSON which didn't help either.
Okay so apparently PowerShell only parses data up to a depth of 2 by default. So as pointed out Here you have to specify the depth with the depth prameter of ConvertTo-Json
I am trying to create a PowerShell script that creates folders (nested and multiple). I managed to get the nested folders to work, and I am pretty new to this, but I cant seem to get the multiple folders to work. I basically want to make a string array with each item being a user inputted string. I tried everything but it just doesn't seem to work, any help would be greatly appreciated, heres my code
echo "Folder Creator for FS2019 by Skaytacium, enter the values for unlimited nested folders! Remember though, 260 characters is the max nesting limit!"
$count = Read-Host -Prompt 'How many folders? Value'
$count = [int16]$count
$sub = Read-Host -Prompt 'Do you wanna nest? 2/1'
$namestring = "./"
$storay
[string]$array = "0", "0", "0"
$arac = 0
$arac = [int16]$arac
if ($sub -eq "2") {
echo "The specified folders will be nested"
while ($count -gt 0) {
$namestring = $namestring + (Read-Host -Prompt 'Name') + "/"
$count--
echo $namestring
if ($count -eq 0) {
md $namestring
}
}
}
elseif ($sub -eq "1") {
echo "The specified folders will be consecutive (in the same dir)"
while ($count -gt 0){
$storay = Read-Host "Name"
$array[1] = #("please help")
echo $array
$arac++
$count--
}
}
Pause
Thanks,
Sid
Replace:
[string] $array = "0", "0", "0" # !! creates *single* string
with:
[string[]] $array = "0", "0", "0" # creates *array* of strings
In your original command, the [string] type constraint to the left of the variable caused the input array (which is implicitly of type [object[]]) to be converted to a single string ([string]) with contents 0 0 0
(PowerShell stringifies arrays by concatenating their elements with separator $OFS, which defaults to a space.)
By contrast, [string[]] results in an array, whose elements are string-typed.
A [...]-enclosed literal in PowerShell refers to a .NET type. It is called a type literal.
If you place such a type literal to the left of variable assignment, you type-constrain that variable, meaning that it can only store values that are instances of that type, and that it automatically tries to convert instances of other types to that type, if possible; see this answer for more information about type constraints.
You can also use a type literal to cast expressions to that type (as your code does in statement $arac = [int16] $arac), which also means converting the operand to that type, as a one-time operation.
In type literal [string[]], the [] after the type name, string, specifies an array of elements of that type; except for the enclosing [...], this notation is the same as the one used by the .NET Type.GetType() method; see this answer for more information.
Note that PowerShell's type conversions are much more flexible than C#'s for instance; they are augmented by built-in conversion rules and attempts to automatically use appropriate constructors and static .Parse() methods.
Note that you don't strictly need the type constraint [string[]], except if you want to make sure that assigning to elements of this array later automatically performs to-string conversion.
# OK, but creates [object[]] array that now happens to contain [string]
# instances, but the type of the elements isn't locked in.
$array = "0", "0", "0"
As for the specific error message you saw:
Because $array was just a single [string] in your code, indexing with, say, [0] indexes in the string's character array.
Technically, this works for getting characters from the array, but you cannot set them:
# GET the 1st character from the string stored in $var
PS> $var = 'foo'; $var[0]
f # 1st character in the string
# !! You CANNOT SET characters that way
PS> $var[0] = 'F'
InvalidOperation: Unable to index into an object of type "System.String".
I've written some pwsh code
"a:b;c:d;e:f".Split(";") | ForEach-Object { $_.Split(":") }
# => #(a, b, c, d, e, f)
but I want this
// in javascript
"a:b;c:d;e:f".split(";").map(str => str.split(":"))
[ [ 'a', 'b' ], [ 'c', 'd' ], [ 'e', 'f' ] ]
a nested array
#(
#(a, b),
#(c, d),
#(e, f),
)
Why? and what should I do
Use the unary form of ,, PowerShell's array-construction operator:
"a:b;c:d;e:f".Split(";") | ForEach-Object { , $_.Split(":") }
That way, the array returned by $_.Split(":") is effectively output as-is, as an array, instead of having its elements output one by one, which happens by default in a PowerShell pipeline.
, creates a - transient - wrapper array whose only element is the array you want to output. PowerShell then unwraps the wrapper array on output, passing the wrapped array through.
With foreach-object, it's usually useful to unwrap the array:
ps | foreach modules | sort -u modulename
You can alternatively create also a stack or a queue.
Below, I created with your array a stack.
$array = "a:b;c:d;e:f".Split(";")
$stack = New-Object -TypeName System.Collections.Stack
$array | ForEach-Object { $stack.Push($_.Split(":")) }
From here, the most used methods are .Push() to insert new items to your stack, .Peek() to use the first item of the stack and .Pop(), to retrieve and then remove the first item.
You mentioned that you wanted to create an array. This is also possible by using the ToArray() method.
$stackArray = $stack.ToArray()
$stackArray[2]
> a
> b
To keep in mind, creating a stack will inverse the order to the items.