Json.psm1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
    'PSAvoidAssignmentToAutomaticVariable', 'IsWindows',
    Justification = 'IsWindows doesnt exist in PS5.1'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
    'PSUseDeclaredVarsMoreThanAssignments', 'IsWindows',
    Justification = 'IsWindows doesnt exist in PS5.1'
)]
[CmdletBinding()]
param()
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
$script:PSModuleInfo = Import-PowerShellDataFile -Path "$PSScriptRoot\$baseName.psd1"
$script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ }
$scriptName = $script:PSModuleInfo.Name
Write-Debug "[$scriptName] - Importing module"

if ($PSEdition -eq 'Desktop') {
    $IsWindows = $true
}

#region [functions] - [public]
Write-Debug "[$scriptName] - [functions] - [public] - Processing folder"
#region [functions] - [public] - [Format-Json]
Write-Debug "[$scriptName] - [functions] - [public] - [Format-Json] - Importing"
function Format-Json {
    <#
        .SYNOPSIS
        Formats a JSON string or PowerShell object.

        .DESCRIPTION
        Converts raw JSON strings or PowerShell objects into formatted JSON. Supports
        pretty-printing with configurable indentation or compact output.

        .EXAMPLE
        Format-Json -JsonString '{"a":1,"b":{"c":2}}' -IndentationType Spaces -IndentationSize 2

        .EXAMPLE
        $obj = @{ user = 'Marius'; roles = @('admin','dev') }
        Format-Json -InputObject $obj -IndentationType Tabs -IndentationSize 1

        .EXAMPLE
        Format-Json -JsonString '{"a":1,"b":{"c":2}}' -Compact

        .LINK
        https://psmodule.io/Json/Functions/Format-Json/
    #>


    [CmdletBinding(DefaultParameterSetName = 'FromString')]
    param (
        # JSON string to format.
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'FromString')]
        [string]$JsonString,

        # PowerShell object to convert and format as JSON.
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'FromObject')]
        [PSObject]$InputObject,

        # Produce compact (minified) output.
        [Parameter(ParameterSetName = 'FromString')]
        [Parameter(ParameterSetName = 'FromObject')]
        [switch]$Compact,

        # Indentation type: 'Spaces' or 'Tabs'.
        [Parameter(ParameterSetName = 'FromString')]
        [Parameter(ParameterSetName = 'FromObject')]
        [ValidateSet('Spaces', 'Tabs')]
        [string]$IndentationType = 'Spaces',

        # Number of spaces or tabs per indentation level. Only used if not compacting.
        [Parameter(ParameterSetName = 'FromString')]
        [Parameter(ParameterSetName = 'FromObject')]
        [UInt16]$IndentationSize = 4
    )

    process {
        try {
            $inputObject = if ($PSCmdlet.ParameterSetName -eq 'FromString') {
                $JsonString | ConvertFrom-Json -ErrorAction Stop
            } else {
                $InputObject
            }

            $json = $inputObject | ConvertTo-Json -Depth 100 -Compress:$Compact

            if ($Compact) {
                return $json
            }

            $indentUnit = switch ($IndentationType) {
                'Tabs' { "`t" }
                'Spaces' { ' ' * $IndentationSize }
            }

            $lines = $json -split "`n"
            $level = 0
            $result = foreach ($line in $lines) {
                $trimmed = $line.Trim()
                if ($trimmed -match '^[}\]]') {
                    $level = [Math]::Max(0, $level - 1)
                }
                $indent = $indentUnit * $level
                $indentedLine = "$indent$trimmed"
                # Check if the line ends with an opening bracket ('[' or '{') and is not a closing bracket ('}' or ']') or a comma.
                # This ensures that the indentation level is increased only for lines that introduce a new block.
                if ($trimmed -match '[{\[]$' -and $trimmed -notmatch '^[}\]],?$') {
                    $level++
                }
                $indentedLine
            }

            return ($result -join "`n")
        } catch {
            Write-Error "Failed to format JSON: $_"
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Format-Json] - Done"
#endregion [functions] - [public] - [Format-Json]
#region [functions] - [public] - [Import-Json]
Write-Debug "[$scriptName] - [functions] - [public] - [Import-Json] - Importing"
function Import-Json {
    <#
        .SYNOPSIS
        Imports JSON data from a file.

        .DESCRIPTION
        Reads JSON content from one or more files and converts it to PowerShell objects.
        Supports pipeline input for processing multiple files.

        .EXAMPLE
        Import-Json -Path 'config.json'

        Imports JSON data from config.json file.

        .EXAMPLE
        Import-Json -Path 'data/*.json'

        Imports JSON data from all .json files in the data directory.

        .EXAMPLE
        'settings.json', 'users.json' | Import-Json

        Imports JSON data from multiple files via pipeline.

        .EXAMPLE
        Import-Json -Path 'complex.json' -Depth 50

        Imports JSON data with a custom maximum depth of 50 levels.

        .LINK
        https://psmodule.io/Json/Functions/Import-Json/
    #>


    [CmdletBinding()]
    param (
        # The path to the JSON file to import. Supports wildcards and multiple paths. Can be provided via pipeline.
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('FullName')]
        [string[]]$Path,

        # The maximum depth to expand nested objects. Uses ConvertFrom-Json default if not specified.
        [Parameter()]
        [int]$Depth
    )

    process {
        foreach ($filePath in $Path) {
            try {
                # Resolve wildcards and relative paths
                $resolvedPaths = Resolve-Path -Path $filePath -ErrorAction Stop

                foreach ($resolvedPath in $resolvedPaths) {
                    Write-Verbose "Processing file: $($resolvedPath.Path)"

                    # Test if the file exists and is a file (not directory)
                    if (-not (Test-Path -Path $resolvedPath.Path -PathType Leaf)) {
                        Write-Error "File not found or is not a file: $($resolvedPath.Path)"
                        continue
                    }

                    # Read file content
                    $jsonContent = Get-Content -Path $resolvedPath.Path -Raw -ErrorAction Stop

                    # Check if file is empty
                    if ([string]::IsNullOrWhiteSpace($jsonContent)) {
                        Write-Warning "File is empty or contains only whitespace: $($resolvedPath.Path)"
                        continue
                    }

                    # Convert JSON to PowerShell object
                    if ($PSBoundParameters.ContainsKey('Depth')) {
                        $jsonObject = $jsonContent | ConvertFrom-Json -Depth $Depth -ErrorAction Stop
                    } else {
                        $jsonObject = $jsonContent | ConvertFrom-Json -ErrorAction Stop
                    }

                    # Add file path information as a note property for reference
                    if ($jsonObject -is [PSCustomObject]) {
                        Add-Member -InputObject $jsonObject -MemberType NoteProperty -Name '_SourceFile' -Value $resolvedPath.Path -Force
                    }

                    # Output the object
                    $jsonObject
                }
            } catch [System.Management.Automation.ItemNotFoundException] {
                Write-Error "Path not found: $filePath"
            } catch [System.ArgumentException] {
                Write-Error "Invalid JSON format in file: $filePath. $_"
            } catch {
                Write-Error "Failed to import JSON from file '$filePath': $_"
            }
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Import-Json] - Done"
#endregion [functions] - [public] - [Import-Json]
Write-Debug "[$scriptName] - [functions] - [public] - Done"
#endregion [functions] - [public]

#region Member exporter
$exports = @{
    Alias    = '*'
    Cmdlet   = ''
    Function = @(
        'Format-Json'
        'Import-Json'
    )
    Variable = ''
}
Export-ModuleMember @exports
#endregion Member exporter