I'm having a difficult time reading in a JSON file in powershell, replacing a value and writing back to the file or a file.
Given the following:
[Object]$QuickJson = #'
{
"swagger": "1.0",
"info": {
"version": "0.0.1",
"title": "this is a title",
"description": "this is a description"
},
"basePath": "/test",
"paths" : {
"/GetSomething": {
"get": {
"name" : "test01"
}
},
"/GetSomethingElse" : {
"get": {
"name" : "test02"
}
},
"/GetAnotherThing": {
"get": {
"name" : "test03"
}
}
}
}
'#
What I'm interested in here is replacing the values in the "paths" object with something else. I read the file and can access the object however with what I'm trying:
[object]$MyPSJson = ConvertFrom-Json -InputObject $QuickJson
foreach($info in $MyPSJson.paths.PSObject.Properties)
{
$path = $info.Name
Write-Host "path: $path"
$info.Name = "$path?code=fsfsfsfsfsfdfds"
Write-Host $info.Name
}
I don't know what those "paths" will be, I need to take the existing value and append a code value to it so I need to be able to iterate through all the paths and do this replacement. When I try the above I get an error:
path: /GetSomething
-- 'Name' is a ReadOnly property. At C:\scripts\test.ps1:44 char:5
+ $info.Name = "$path?code=fsfsfsfsfsfdfds"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
I've tried several different things and yet to come up with a good, or workable for that matter, solution. Any help or pointing in the right direction is greatly appreciated.
You could do something very similar to what you have now, just instead of changing the existing properties you record the initial properties, add new ones with the modifications you want, then set $MyPSJson.paths to itself excluding the old properties, so all it has are the new properties.
#Find initial paths
$Paths=$MyPSJson.paths.psobject.Properties.Name
#Add new paths with the modification to the name, and set the value to the same as the old path
$Paths|%{Add-Member -InputObject $MyPSJson.paths -NotePropertyName "$_`?code=fsfsfsfsfsfdfds" -NotePropertyValue $MyPSJson.paths.$_}
#Set the paths object to itself, excluding the original paths
$MyPSJson.paths = $MyPSJson.paths|Select * -ExcludeProperty $Paths
Try the following (PSv3+):
$MyPSJson.paths = $MyPSJson.paths.psobject.properties |
ForEach-Object { $renamedProps = [ordered] #{} } {
$renamedProps[$_.Name + '?foo=bar&baz=bam'] = $_.Value
} { [pscustomobject] $renamedProps }
Of technically necessity, this recreates the .paths object with modified property names.
It does so by enumerating the original properties, adding their values under the modified name to an ordered hashtable, which on completion of the pipeline is converted to a custom object ([pscustomobject]).
As for what you tried:
A given object's properties cannot be renamed - only its properties' values can be changed.
Therefore, you must create a new object with the desired new property names and the same values as the original.
As an aside: an [object] cast is pointless in PowerShell - it is an effective no-op.
By contrast, a [pscustomobject] cast can be used to create [pscustomobject] instances from [ordered] hashtables, which is the technique used above; casting from other types is again a virtual no-op.
In addition to the accepted answer which worked for me I also found another way which was initially suggested which was to use a hashtable:
function Convert-JsonFileToHashTable([string]$file) {
$text = [IO.File]::ReadAllText($file)
$parser = New-Object Web.Script.Serialization.JavaScriptSerializer
$parser.MaxJsonLength = $text.length
$hashtable = $parser.Deserialize($text, #{}.GetType())
return $hashtable
}
$hashtable = Convert-JsonFileToHashTable (Resolve-Path -Path $DefinitionFile)
$keys = New-Object System.Collections.ArrayList($null)
$keys.AddRange($hashtable["paths"].Keys)
foreach ($key in $keys)
{
$newPath = $key + "?code=$Code"
$hashtable["paths"].$newPath = $hashtable["paths"].$key
$hashtable["paths"].Remove($key) | Out-Null
}
$json = $hashtable | ConvertTo-Json -Depth 20
Write-Output $json | Out-File $newFile -Encoding utf8
Related
One requirement of mine is - Using windows, not use any tools not already available as part of aws cli or windows
For example, I have this json file test.json with below content:
"My number is $myvar"
I read this into a powershell variable like so:
$myobj=(get-content .\test.json | convertfrom-json)
$myvar=1
From here, I would like to do something with this $myobj which will enable me to get this output:
$myobj | tee json_with_values_from_environment.json
My number is 1
I got some limited success with iex, but not sure if it can be made to work for this example
You can use $ExecutionContext.InvokeCommand.ExpandString()
$myobj = '{test: "My number is $myvar"}' | ConvertFrom-Json
$myvar = 1
$ExecutionContext.InvokeCommand.ExpandString($myobj.test)
Output
My number is 1
Here is one way to do it using the Parser to find all VariableExpressionAst and replace them with the values in your session.
Given the following test.json:
{
"test1": "My number is $myvar",
"test2": {
"somevalue": "$env:myothervar",
"someothervalue": "$anothervar !!"
}
}
We want to find and replace $myvar, $myothervar and $anothervar with their corresponding values defined in the current session, so the code looks like this (note that we do the replacement before converting the Json string into an object, this way is much easier):
using namespace System.Management.Automation.Language
$isCore7 = $PSVersionTable.PSVersion -ge '7.2'
# Define the variables here
$myvar = 10
$env:myothervar = 'hello'
$anothervar = 'world'
# Read the Json
$json = Get-Content .\test.json -Raw
# Now parse it
$ast = [Parser]::ParseInput($json, [ref] $null, [ref] $null)
# Find all variables in it, and enumerate them
$ast.FindAll({ $args[0] -is [VariableExpressionAst] }, $true) |
Sort-Object { $_.Extent.Text } -Unique | ForEach-Object {
# now replace the text with the actual value
if($isCore7) {
# in PowerShell Core is very easy
$json = $json.Replace($_.Extent.Text, $_.SafeGetValue($true))
return
}
# in Windows PowerShell not so much
$varText = $_.Extent.Text
$varPath = $_.VariablePath
# find the value of the var (here we use the path)
$value = $ExecutionContext.SessionState.PSVariable.GetValue($varPath.UserPath)
if($varPath.IsDriveQualified) {
$value = $ExecutionContext.SessionState.InvokeProvider.Item.Get($varPath.UserPath).Value
}
# now replace the text with the actual value
$json = $json.Replace($varText, $value)
}
# now we can safely convert the string to an object
$json | ConvertFrom-Json
If we were to convert it back to Json to see the result:
{
"test1": "My number is 10",
"test2": {
"somevalue": "hello",
"someothervalue": "world !!"
}
}
I feel like this is something simple and I'm just not getting it, and I'm not sure if my explanation is great.
I have this below JSON file, and I want to get "each App" (App1, App2, App3) under the "New" object
In this script line below I'm essentially trying to replace "TestApp2" with some variable. I guess I'm trying to get TestApp2 as an object without knowing the name.
And I realize that the foreach loop doesn't do anything right now
Write-Host $object.Value.TestApp2.reply_urls
JSON:
{
"New": {
"App1": {
"reply_urls": [
"https://testapp1url1"
]
},
"App2": {
"reply_urls": [
"https://testapp2url1",
"https://testapp2url2"
]
},
"App3": {
"reply_urls": [
"https://testapp3url1",
"https://testapp3url2",
"https://testapp3url3"
]
}
},
"Remove": {
"object_id": [
""
]
}
}
Script:
$inputFile = Get-Content -Path $inputFilePath -Raw | ConvertFrom-Json
foreach ($object in $inputFile.PsObject.Properties)
{
switch ($object.Name)
{
New
{
foreach ($app in $object.Value)
{
Write-Host $object.Value.TestApp2.reply_urls
# essentially want to replace this line with something like
# Write-Host $app.reply_urls
}
}
Remove
{
}
}
}
Output:
https://testapp2url1 https://testapp2url2
You can access the object's PSObject.Properties to get the property Names and property Values, which you can use to iterate over.
For example:
foreach($obj in $json.New.PSObject.Properties) {
$out = [ordered]#{ App = $obj.Name }
foreach($url in $obj.Value.PSObject.Properties) {
$out[$url.Name] = $url.Value
}
[pscustomobject] $out
}
Produces the following output:
App reply_urls
--- ----------
App1 {https://testapp1url1}
App2 {https://testapp2url1, https://testapp2url2}
App3 {https://testapp3url1, https://testapp3url2, https://testapp3url3}
If you just want to output the URL you can skip the construction of the PSCustomObject:
foreach($obj in $json.New.PSObject.Properties) {
foreach($url in $obj.Value.PSObject.Properties) {
$url.Value
}
}
Complementing Santiago Squarzon's helpful answer, I've looked for a generalized approach to get JSON properties without knowing the names of their parents in advance.
Pure PowerShell solution
I wrote a little helper function Expand-JsonProperties that "flattens" the JSON. This allows us to use simple non-recursive Where-Object queries for finding properties, regardless how deeply nested they are.
Function Expand-JsonProperties {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)] [PSCustomObject] $Json,
[Parameter()] [string] $Path,
[Parameter()] [string] $Separator = '/'
)
process {
$Json.PSObject.Properties.ForEach{
$propertyPath = if( $Path ) { "$Path$Separator$($_.Name)" } else { $_.Name }
if( $_.Value -is [PSCustomObject] ) {
Expand-JsonProperties $_.Value $propertyPath
}
else {
[PSCustomObject]#{
Path = $propertyPath
Value = $_.Value
}
}
}
}
}
Given your XML sample we can now write:
$inputFile = Get-Content -Path $inputFilePath -Raw | ConvertFrom-Json
$inputFile | Expand-JsonProperties | Where-Object Path -like '*/reply_urls'
Output:
Path Value
---- -----
New/App1/reply_urls {https://testapp1url1}
New/App2/reply_urls {https://testapp2url1, https://testapp2url2}
New/App3/reply_urls {https://testapp3url1, https://testapp3url2, https://testapp3url3}
Optimized solution using inline C#
Out of curiosity I've tried out a few different algorithms, including ones that don't require recursion.
One of the fastest algorithms is written in inline C# but can be called through an easy to use PowerShell wrapper cmdlet (see below). The C# code basically works the same as the pure PowerShell function but turned out to be more than 9 times faster!
This requires at least PowerShell 7.x.
# Define inline C# class that does most of the work.
Add-Type -TypeDefinition #'
using System;
using System.Collections.Generic;
using System.Management.Automation;
public class ExpandPSObjectOptions {
public bool IncludeObjects = false;
public bool IncludeLeafs = true;
public string Separator = "/";
}
public class ExpandPSObjectRecursive {
public static IEnumerable< KeyValuePair< string, object > > Expand(
PSObject inputObject, string parentPath, ExpandPSObjectOptions options ) {
foreach( var property in inputObject.Properties ) {
var propertyPath = parentPath + options.Separator + property.Name;
if( property.Value is PSObject ) {
if( options.IncludeObjects ) {
yield return new KeyValuePair< string, object >( propertyPath, property.Value );
}
// Recursion
foreach( var prop in Expand( (PSObject) property.Value, propertyPath, options ) ) {
yield return prop;
}
continue;
}
if( options.IncludeLeafs ) {
yield return new KeyValuePair< string, object >( propertyPath, property.Value );
}
}
}
}
'#
# A PowerShell cmdlet that wraps the C# class.
Function Expand-PSObjectRecursive {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)] [PSObject] $InputObject,
[Parameter()] [string] $Separator = '/',
[Parameter()] [switch] $IncludeObjects,
[Parameter()] [switch] $ExcludeLeafs
)
process {
$options = [ExpandPSObjectOptions]::new()
$options.IncludeObjects = $IncludeObjects.ToBool()
$options.IncludeLeafs = -not $ExcludeLeafs.ToBool()
$options.Separator = $Separator
[ExpandPSObjectRecursive]::Expand( $InputObject, '', $options )
}
}
The C# code is wrapped by a normal PowerShell cmdlet, so you can basically use it in the same way as the pure PowerShell function, with minor syntactic differences:
$inputFile | Expand-PSObjectRecursive | Where-Object Key -like '*/reply_urls'
I've added some other useful parameters that allows you to define the kind of elements that the cmdlet should output:
$inputFile |
Expand-PSObjectRecursive -IncludeObjects -ExcludeLeafs |
Where-Object Key -like '*/App*'
Parameter -IncludeObjects also includes PSObject properties from the input, while -ExcludeLeafs excludes the value-type properties, resulting in this output:
Key Value
--- -----
/New/App1 #{reply_urls=System.Object[]}
/New/App2 #{reply_urls=System.Object[]}
/New/App3 #{reply_urls=System.Object[]}
While the table format output in itself is not too useful, you could use the output objects for further processing, e. g.:
$apps = $inputFile |
Expand-PSObjectRecursive -IncludeObjects -ExcludeLeafs |
Where-Object Key -like '*/App*'
$apps.Value.reply_urls
Prints:
https://testapp1url1
https://testapp2url1
https://testapp2url2
https://testapp3url1
https://testapp3url2
https://testapp3url3
Implementation notes:
The C# code uses the yield return statement to return properties one-by-one, similar to what we are used from PowerShell pipelines. In inline C# code we can't use the pipeline directly (and it wouldn't be advisable for performance reasons), but yield return allows us to smoothly interface with PowerShell code that can be part of a pipeline.
PowerShell automatically inserts the return values of the C# function into the success output stream, one by one. The difference to returning an array from C# is that we may exit early at any point without having to process the whole input object first (e. g. using Select -First n). This would actually cancel the C# function, something that would otherwise only be possible using multithreading (with all its complexities)! But all of this is just single-threaded code.
how can I check if the Object 'Production' in my json-File exists in Powershell?
an extract of my json File:
[
{
"UID": "x,
"Office": "xy",
"Production": "a"
}
]
In this case the key "Production" does exist in the json File. But that is not always the case.
How can I check if it exists via Powershell? With Get-Member?
my approach:
$json = Get-Content "C:\file.json" | ConvertFrom-Json
foreach ($item in $json) {
if (Get-Member -InputObject $item.Production) {Write-Host "Production exists"}
else {Write-Host "Production does not exist"}
}
THANKS!
You could check if the property exists by accessing the object's PSObject.Properties, one of the many ways to validate could be using the .Item(..) method:
$json = '[{ "Production": "Hello" }, { "OtherProp": "Nothing Here" }]' | ConvertFrom-Json
$count = 0
foreach($item in $json) {
$index = 'Object {0}' -f $count++
if($prop = $item.PSObject.Properties.Item('Production')) {
"{0}: Production Exists and it's value is: '{1}'" -f $index, $prop.Value
}
else {
"{0}: No property with name Production" -f $index
}
}
You can also use the property name to index to the NoteProperty Object with the following syntax:
if($prop = $item.PSObject.Properties['Production']) {
The first object from the example Json has a property with Name Production and Hello as Value while the second object does have the specified property. Above would result in:
Object 0: Production Exists and it's value is: 'Hello'
Object 1: No property with name Production
I am writing a PowerShell Script, which will read a json file having different sections, like job1, job2 and so on.. Now my objective is to read each section separately and to loop through it as a key value pair. I also need to maintain the order of the input file, because the jobs are scheduled in sequence. and these jobs run taking the values from the json file as input.
I tried using Powershell version 5.1, in which I created PSCustomObject but the order is getting sorted alphabetically, which I DON'T want.
Json File :
{ "Job1": [
{
"Ram" : "India",
"Anthony" : "London",
"Elena" : "Zurich"
}],
"Job2": [
{
"Build" : "fail",
"Anthony" : "right",
"Sam" : "left"
}]}
$json = Get-Content -Path C:\PowershellScripts\config_File.json |
ConvertFrom-Json
$obj = $json.Job1
$json.Job1 | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
$values = [PSCustomObject][ordered]#{Key = $key; Value = $obj."$key"}
$values
}
I am expecting to loop through each section separately and in the same order that's provided in the json file. For example looping through Job1 section and to fetch only the Values in the same order that's in the json file.
I will guarantee that this is not the best way to do this, but it works.
$json = Get-Content -Path C:\PowershellScripts\config_File.json |
ConvertFrom-Json
$out = ($json.Job1 | Format-List | Out-String).Trim() -replace "\s+(?=:)|(?<=:)\s+"
$out -split "\r?\n" | ForEach-Object {
[PSCustomObject]#{Key = $_.Split(":")[0]; Value = $_.Split(":")[1]}
}
Explanation:
The JSON object is first output using Format-List to produce the Property : Value format, which is piped to Out-String to make that output a single string. Trim() is used to remove surrounding white space.
The -replace removes all white space before and after : characters.
The -split \r?\n splits the single string into an array of lines. Each of those lines is then split by the : character (.Split(":")). The [0] index selects the string on the left side of the :. The [1] selects the string on the right side of the :.
Can you change the json schema?
I would probably make changes to the json schema before i tried to parse this (if possible of course).
Like this (changed only Job1):
$json = #"
{ "Job1": [
{
"Name": "Ram",
"Location" : "India"
},
{
"Name": "Anthony",
"Location": "London"
},
{
"Name": "Elena" ,
"Location": "Zurich"
}
],
"Job2": [
{
"Build" : "fail",
"Anthony" : "right",
"Sam" : "left"
}]}
"# | convertfrom-json
foreach ($obj in $json.Job1) {
$key = $obj.Name
$values = [PSCustomObject][ordered]#{Key = $key; Value = $obj."$key" }
$values
}
I have imported some JSON data and converted it to a PowerShell Object. I would like to understand how to retrieve specific portions of said data.
test.json:
{
"Table": {
"Users": {
"Columns": [ "[Id]",
"[FName]",
"[MName]",
"[SName]",
"[UName]",
"[Pasword]" ],
"data": "CustomUserData"
},
"Roles": {
"Columns": [ "[Id]",
"[Role]",
"[Description]" ],
"data": "CustomRoleData"
}
}
}
Import to PS Object:
$userdata = Get-Content .\test.json |ConvertFrom-Json
Retrieve and format column data:
PS> $userdata = Get-Content ./test.json |ConvertFrom-Json
PS> $columns = $userdata.Table.Users.Columns -join ","
PS> $columns
[Id],[FName],[MName],[SName],[UName],[Pasword]
Example retrieval of custom data:
PS> $userdata.Table.Users.data
CustomUserData
What I would like to do is:
Select just the table names. When I try and do this by calling $userdata.table I get the following:
PS> $userdata.Table |Format-List
Users : #{Columns=System.Object[]; data=CustomUserData}
Roles : #{Columns=System.Object[]; data=CustomRoleData}
What I am looking for is just a list of the table names, in this case - Users,Roles
I would also like to know how to leverage this to create a ForEach loop which cycles through each table name and prints the columns associated with each table - ultimately I will be using this to craft a SQL query.
Thank you!
Maybe this can help you.
It is a small function to output the property names recursively.
function Get-Properties($obj, [int]$level = 0) {
$spacer = " "*$level
$obj.PSObject.Properties | ForEach-Object {
$spacer + $_.Name
if ($_.Value -is [PSCustomObject]){
Get-Properties $_.Value ($level + 2)
}
}
}
In your case, you can use it like this:
$userdata = Get-Content ./test.json | ConvertFrom-Json
Get-Properties $userData
The console output will look like this:
Table
Users
Columns
data
Roles
Columns
data