Source/Public/ConvertFrom-Expression.ps1

<#
.SYNOPSIS
    Deserializes a PowerShell expression to an object.
 
.DESCRIPTION
    The `ConvertFrom-Expression` cmdlet safely converts a PowerShell formatted expression to an object-graph
    existing of a mixture of nested arrays, hashtables and objects that contain a list of strings and values.
 
.PARAMETER InputObject
    Specifies the PowerShell expressions to convert to objects. Enter a variable that contains the string,
    or type a command or expression that gets the string. You can also pipe a string to ConvertFrom-Expression.
 
    The **InputObject** parameter is required, but its value can be an empty string.
    The **InputObject** value can't be `$null` or an empty string.
 
.PARAMETER LanguageMode
    Defines which object types are allowed for the deserialization, see: [About language modes][2]
 
    * Any type that is not allowed by the given language mode, will be omitted leaving a bare `[ValueType]`,
      `[String]`, `[Array]` or `[HashTable]`.
    * Any variable that is not `$True`, `$False` or `$Null` will be converted to a literal string, e.g. `$Test`.
 
    > [!Caution]
    >
    > In full language mode, `ConvertTo-Expression` permits all type initializers. Cmdlets, functions,
    > CIM commands, and workflows will *not* be invoked by the `ConvertFrom-Expression` cmdlet.
    >
    > Take reasonable precautions when using the `Invoke-Expression -LanguageMode Full` command in scripts.
    > Verify that the class types in the expression are safe before instantiating them. In general, it is
    > best to design your configuration expressions with restricted or constrained classes, rather than
    > allowing full freeform expressions.
 
.PARAMETER ListAs
    If supplied, the array subexpression `@( )` syntaxes without an type initializer or with an unknown
    or denied type initializer will be converted to the given list type.
 
.PARAMETER MapAs
    If supplied, the Hash table literal syntax `@{ }` syntaxes without an type initializer or with an unknown
    or denied type initializer will be converted to the given map (dictionary or object) type.
 
#>


function ConvertFrom-Expression {
    [CmdletBinding(HelpUri='https://github.com/iRon7/ObjectGraphTools/blob/main/Docs/ConvertFrom-Expression.md')][OutputType([Object])] param(

        [Parameter(Mandatory = $true, ValueFromPipeLine = $True)]
        [Alias('Expression')][String]$InputObject,

        [ValidateScript({ $_ -ne 'NoLanguage' })]
        [System.Management.Automation.PSLanguageMode]$LanguageMode = 'Restricted',

        [ValidateNotNull()]$ListAs,

        [ValidateNotNull()]$MapAs
    )

    begin {
        function StopError($Exception, $Id = 'IncorrectArgument', $Group = [Management.Automation.ErrorCategory]::SyntaxError, $Object){
            if ($Exception -is [System.Management.Automation.ErrorRecord]) { $Exception = $Exception.Exception }
            elseif ($Exception -isnot [Exception]) { $Exception = [ArgumentException]$Exception }
            $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new($Exception, $Id, $Group, $Object))
        }

        if ($this.LanguageMode -eq 'NoLanguage') { Throw 'The language mode "NoLanguage" is not supported.' }

        $ListNode = if ($ListAs) { [PSNode]::ParseInput([PSInstance]::Create($ListAs)) }
        $MapNode  = if ($MapAs)  { [PSNode]::ParseInput([PSInstance]::Create($MapAs)) }

        if (
            $ListNode -is [PSMapNode] -and $MapNode -is [PSListNode] -or
            -not $ListNode -and $MapNode -is [PSListNode] -or
            $ListNode -is [PSMapNode] -and -not $MapNode
        ) {
            $ListNode, $MapNode = $MapNode, $ListNode # In case the parameter positions are swapped
        }

        $ListType = if ($ListNode) {
            if ($ListType -is [PSListNode]) { $ListNode.ValueType }
            else { StopError 'The -ListAs parameter requires a string, type or an object example that supports a list structure' }
        }

        $MapType = if ($MapNode) {
            if ($MapNode -is [PSMapNode]) { $MapNode.ValueType }
            else { StopError 'The -MapAs parameter requires a string, type or an object example that supports a map structure' }
        }
        if ('System.Management.Automation.PSCustomObject' -eq $MapNode.ValueType) { $MapType = 'PSCustomObject' -as [type] } # https://github.com/PowerShell/PowerShell/issues/2295

    }

    process {
        [PSDeserialize]::new($InputObject, $LanguageMode, $ListType, $MapType).Object
    }
}