I am writing a PowerShell function to build the JSON that will be passed to an API. I am using PowerShell 5.
The API expects a certain format and an example is below.
{
“task”: {
“status”: {
“name”: “Resolved”
},
“owner”: {
“name”: “User”
},
“comment”: “Test”
}
}
The structure of the JSON is stored in nested hash tables then piped to ConvertTo-Json and I am passing the data values through parameters.
$baseTask = @{
task = @{
comment = $Comment;
status = @{
name = $Status;
owner = @{
name = $Owner;
}
}
}
I want the function to be as dynamic as possible so I have the identifier as mandatory and then all other parameters as optional.
Param(
[Parameter(Mandatory=$true)]
$TaskID,
$Comment,
$Status,
$Owner
)
If the parameter is NULL I want to remove that element from the definition so that it doesn’t pass NULL to the API. I have the logic below.
If([String]::IsNullOrEmpty($Comment)) {
$baseTask.task.Remove(‘comment’)
}
If([String]::IsNullOrEmpty($Status)) {
$baseTask.task.Remove(‘status’)
}
If([String]::IsNullOrEmpty($Owner)) {
$baseTask.task.Remove(‘owner’)
}
This works; however if I have 50 parameters then I am going to have a lot of repetition with the null checks so I feel there must be a more dynamic way to do this ideally with some sort of loop.
I thought about a hash table with mapping between variable name and command to run, but I can’t find a way to get the name of the variable during execution.
Any help would be much appreciated.
CodePudding user response:
You can take something like that with care. The idea is to convert names ('status') to paths where it should be ('status.name') and then get a hashtable like
'id' = 22
'status.name' = 'new'
'comment' = 'text'
'owner.name' = 'user'
and convert it to multi-layered hashtable based on "dot" separator then:
$baseTask = @{
task = @{
comment = $Comment;
status = @{
name = $Status;
owner = @{
name = $Owner;
}
}
}
And then to JSON
The example below is an example and should be used with caution as it HAS some problems.
function test {
[CMDLetBinding()]
Param(
[Parameter(Mandatory=$true)]
$TaskID,
$Comment,
$Status,
$Owner
)
$paramList = @{}
$params = $PSCmdlet.MyInvocation.BoundParameters
foreach ($paramName in $params.Keys) {
switch -Exact ($paramName) {
'TaskID' { $paramList['id'] = $params[$paramName] }
'Comment' { $paramList['comment'] = $params[$paramName] }
'Status' { $paramList['status.name'] = $params[$paramName] }
'Owner' { $paramList['owner.name'] = $params[$paramName] }
}
}
$replaced = 0
do {
$replaced = 0
$keys = @($paramList.Keys)
foreach ($key in $keys) {
$dotPos = $key.LastIndexOf('.')
if ($dotPos -gt 0) {
$value = $paramList[$key]
if (-not $value.GetType().IsValueType) {
if ($value.Clone -ne $null) {
$value = $value.Clone()
}
}
$subKey = $key.Substring($dotPos 1)
$newKey = $key.Substring(0, $dotPos)
$paramList.Remove($key) | Out-Null
$newVal = @{$subKey=$value}
$paramList.Add($newKey, $newVal)
$replaced
}
}
} while ($replaced -gt 0)
return $paramList
}
test -TaskID 123 -Status 'New' -Comment 'Test' -Owner 'user' | ConvertTo-Json -Depth 10 -Compress:$false
CodePudding user response:
You may check to see which parameters were not used by comparing the bound parameters against all available parameters for your command and then use this to remove the different elements from your hash table.
function New-DynamicJson {
Param(
[Parameter(Mandatory = $true)]
$TaskID,
$Comment,
$Status,
$Owner
)
$baseTask = @{
task = [ordered]@{
Id = $TaskID;
comment = $Comment;
status = @{
name = $Status;
}
owner = @{
name = $Owner;
}
}
}
$commonParams = 'Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'InformationAction', 'ErrorVariable',
'WarningVariable', 'InformationVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable'
$MyInvocation.MyCommand.Parameters.Keys.Where({ $_ -notin $commonParams }) |
Where-Object { $_ -notin $PSBoundParameters.Keys } |
ForEach-Object { $baseTask.task.Remove($_) }
$baseTask | ConvertTo-Json
}
Output
PS> New-DynamicJson -TaskID 103 -Comment 'some comment'
{
"task": {
"Id": 103,
"comment": "some comment"
}
}
(Thank you mklement0 for the suggestion to use $MyInvocation
instead of $PSCmdlet.MyInvocation
which also makes the solution work in non-advanced functions)