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] - [Export-Json] Write-Debug "[$scriptName] - [functions] - [public] - [Export-Json] - Importing" function Export-Json { <# .SYNOPSIS Exports JSON data to a file. .DESCRIPTION Converts PowerShell objects to JSON format and writes them to one or more files. Supports various formatting options including indentation types, sizes, and compact output. Can accept both PowerShell objects and JSON strings as input. .EXAMPLE Export-Json -InputObject $myObject -Path 'output.json' Exports a PowerShell object to output.json with default formatting. .EXAMPLE Export-Json -InputObject $data -Path 'config.json' -IndentationType Spaces -IndentationSize 2 Exports data to config.json with 2-space indentation. .EXAMPLE Export-Json -JsonString $jsonText -Path 'data.json' -Compact Exports a JSON string to data.json in compact format. .EXAMPLE $objects | Export-Json -Path 'output.json' Exports multiple objects to the same file via pipeline (last object overwrites). .EXAMPLE Export-Json -InputObject $config -Path 'settings.json' -IndentationType Tabs -Force Exports configuration to settings.json with tab indentation, overwriting if it exists. .LINK https://psmodule.io/Json/Functions/Export-Json/ #> [CmdletBinding(DefaultParameterSetName = 'FromObject', SupportsShouldProcess)] param ( # PowerShell object to convert and export as JSON. [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'FromObject')] [PSObject]$InputObject, # JSON string to export to file. [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'FromString')] [string]$JsonString, # The path to the output JSON file. [Parameter(Mandatory)] [string]$Path, # Produce compact (minified) output. [Parameter()] [switch]$Compact, # Indentation type: 'Spaces' or 'Tabs'. [Parameter()] [ValidateSet('Spaces', 'Tabs')] [string]$IndentationType = 'Spaces', # Number of spaces or tabs per indentation level. Only used if not compacting. [Parameter()] [UInt16]$IndentationSize = 2, # The maximum depth to serialize nested objects. [Parameter()] [int]$Depth = 2, # Overwrite existing files without prompting. [Parameter()] [switch]$Force, # Text encoding for the output file. [Parameter()] [ValidateSet('ASCII', 'BigEndianUnicode', 'BigEndianUTF32', 'OEM', 'Unicode', 'UTF7', 'UTF8', 'UTF8BOM', 'UTF8NoBOM', 'UTF32')] [string]$Encoding = 'UTF8NoBOM' ) begin { } process { try { # Determine the input object $objectToExport = if ($PSCmdlet.ParameterSetName -eq 'FromString') { $JsonString | ConvertFrom-Json -Depth $Depth -ErrorAction Stop } else { $InputObject } # Generate the file path $outputPath = $Path # Resolve the path for consistent operations and error messages if (Test-Path -Path $outputPath) { $resolvedPath = Resolve-Path -Path $outputPath } else { # For non-existing files, resolve the parent directory and combine with filename $parentPath = Split-Path -Path $outputPath -Parent $fileName = Split-Path -Path $outputPath -Leaf if ($parentPath -and (Test-Path -Path $parentPath)) { $resolvedParent = Resolve-Path -Path $parentPath $resolvedPath = Join-Path -Path $resolvedParent -ChildPath $fileName } else { # If parent doesn't exist either, use the original path as-is for error messages $resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($outputPath) } } # Check if file exists and handle accordingly if ((Test-Path -Path $resolvedPath -PathType Leaf) -and -not $Force) { if ($PSCmdlet.ShouldProcess($resolvedPath, "Overwrite existing file")) { # Continue with export } else { # Only error if not WhatIf - WhatIf should just show what would happen if (-not $WhatIfPreference) { Write-Error "File already exists: $resolvedPath. Use -Force to overwrite." } return } } # Create directory if it doesn't exist $directory = Split-Path -Path $resolvedPath -Parent if ($directory -and -not (Test-Path -Path $directory -PathType Container)) { Write-Verbose "Creating directory: $directory" $null = New-Item -Path $directory -ItemType Directory -Force } # Format the JSON if ($Compact) { $formattedJson = $objectToExport | ConvertTo-Json -Depth $Depth -Compress } else { # Use Format-Json for consistent formatting $formattedJson = Format-Json -InputObject $objectToExport -IndentationType $IndentationType -IndentationSize $IndentationSize } # Write to file if ($PSCmdlet.ShouldProcess($resolvedPath, "Export JSON")) { Write-Verbose "Exporting JSON to: $resolvedPath" $writeParams = @{ Path = $resolvedPath Value = $formattedJson Encoding = $Encoding } # Only use Force for Set-Content if user explicitly requested it if ($Force) { $writeParams['Force'] = $true } Set-Content @writeParams -ErrorAction Stop # Output file info object Get-Item -Path $resolvedPath | Add-Member -MemberType NoteProperty -Name 'JsonExported' -Value $true -PassThru } } catch [System.ArgumentException] { Write-Error "Invalid JSON format: $_" } catch [System.IO.DirectoryNotFoundException] { Write-Error "Directory not found or could not be created: $directory" } catch [System.UnauthorizedAccessException] { Write-Error "Access denied: $resolvedPath" } catch { Write-Error "Failed to export JSON to '$resolvedPath': $_" } } } Write-Debug "[$scriptName] - [functions] - [public] - [Export-Json] - Done" #endregion [functions] - [public] - [Export-Json] #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 = 2 ) 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 = @( 'Export-Json' 'Format-Json' 'Import-Json' ) Variable = '' } Export-ModuleMember @exports #endregion Member exporter |