1

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.

3
  • 1
    $PSCmdlet.MyInvocation.MyCommand.Parameters.Keys | Where-Object { $_ -notin $PSBoundParameters.Keys } | ForEach-Object { $baseTask.task.Remove($_) } Commented Oct 10, 2021 at 7:43
  • Thanks Daniel. Will look at this. Commented Oct 10, 2021 at 16:21
  • 1
    Very clever, @Daniel - why don't you write that up as an answer? (You can use $MyInvocation..., which also makes the solution work in non-advanced functions). Commented Oct 10, 2021 at 19:09

2 Answers 2

2

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)

Sign up to request clarification or add additional context in comments.

Comments

-1

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

2 Comments

Thanks for your answer filimonic, but doesn’t this just mean that I will have to write a switch statement for each parameter instead of an null check?
You HAVE to describe each parameter somehow because you have different processing for them: "Status" mecomes "Status\Name", but "Comment" does not become "Comment\Name", etc. You of course can use some "default" for parameters you did not specified before.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.