Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion examples/General.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# JSON Module Examples
# This file contains practical examples of using the Format-Json function
# This file contains practical examples of using the Format-Json and Import-Json functions

# Import the module (if not already loaded)
# Import-Module Json
Expand Down Expand Up @@ -146,4 +146,81 @@ $formatted

#endregion

#endregion

#region Import-Json Examples

# Example 9: Import JSON from a single file
'Example 9: Import JSON from a single file'
# First, create a sample JSON file
$configData = @{
database = @{
host = 'localhost'
port = 5432
name = 'myapp'
ssl = $true
}
logging = @{
level = 'info'
file = '/var/log/app.log'
}
features = @{
caching = $true
analytics = $false
}
}
$configFile = '/tmp/config.json'
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using hardcoded Unix-style paths like '/tmp/' will fail on Windows systems. Consider using Join-Path $env:TEMP 'config.json' or [System.IO.Path]::GetTempPath() for cross-platform compatibility.

Suggested change
$configFile = '/tmp/config.json'
$configFile = Join-Path $env:TEMP 'config.json'

Copilot uses AI. Check for mistakes.
$configData | ConvertTo-Json -Depth 3 | Set-Content -Path $configFile

# Import the JSON file
$importedConfig = Import-Json -Path $configFile
$importedConfig
"Database host: $($importedConfig.database.host)"
"Source file: $($importedConfig._SourceFile)"

# Example 10: Import multiple JSON files using wildcards
'Example 10: Import multiple JSON files using wildcards'
# Create multiple JSON files
$userData = @{ name = 'Alice'; role = 'admin'; active = $true }
$settingsData = @{ theme = 'dark'; notifications = $true; language = 'en' }

$userFile = '/tmp/user-data.json'
$settingsFile = '/tmp/user-settings.json'

$userData | ConvertTo-Json | Set-Content -Path $userFile
$settingsData | ConvertTo-Json | Set-Content -Path $settingsFile

# Import all user-*.json files
$allUserData = Import-Json -Path '/tmp/user-*.json'
Comment on lines +187 to +194
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using hardcoded Unix-style paths like '/tmp/' will fail on Windows systems. Consider using Join-Path $env:TEMP 'user-data.json' for cross-platform compatibility.

Suggested change
$userFile = '/tmp/user-data.json'
$settingsFile = '/tmp/user-settings.json'
$userData | ConvertTo-Json | Set-Content -Path $userFile
$settingsData | ConvertTo-Json | Set-Content -Path $settingsFile
# Import all user-*.json files
$allUserData = Import-Json -Path '/tmp/user-*.json'
$userFile = Join-Path $env:TEMP 'user-data.json'
$settingsFile = Join-Path $env:TEMP 'user-settings.json'
$userData | ConvertTo-Json | Set-Content -Path $userFile
$settingsData | ConvertTo-Json | Set-Content -Path $settingsFile
# Import all user-*.json files
$allUserData = Import-Json -Path (Join-Path $env:TEMP 'user-*.json')

Copilot uses AI. Check for mistakes.
Comment on lines +187 to +194
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using hardcoded Unix-style paths like '/tmp/' will fail on Windows systems. Consider using Join-Path $env:TEMP 'user-settings.json' for cross-platform compatibility.

Suggested change
$userFile = '/tmp/user-data.json'
$settingsFile = '/tmp/user-settings.json'
$userData | ConvertTo-Json | Set-Content -Path $userFile
$settingsData | ConvertTo-Json | Set-Content -Path $settingsFile
# Import all user-*.json files
$allUserData = Import-Json -Path '/tmp/user-*.json'
$userFile = Join-Path $env:TEMP 'user-data.json'
$settingsFile = Join-Path $env:TEMP 'user-settings.json'
$userData | ConvertTo-Json | Set-Content -Path $userFile
$settingsData | ConvertTo-Json | Set-Content -Path $settingsFile
# Import all user-*.json files
$allUserData = Import-Json -Path (Join-Path $env:TEMP 'user-*.json')

Copilot uses AI. Check for mistakes.
Comment on lines +187 to +194
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using hardcoded Unix-style paths like '/tmp/' will fail on Windows systems. Consider using Join-Path $env:TEMP 'user-*.json' for cross-platform compatibility.

Suggested change
$userFile = '/tmp/user-data.json'
$settingsFile = '/tmp/user-settings.json'
$userData | ConvertTo-Json | Set-Content -Path $userFile
$settingsData | ConvertTo-Json | Set-Content -Path $settingsFile
# Import all user-*.json files
$allUserData = Import-Json -Path '/tmp/user-*.json'
$userFile = Join-Path $env:TEMP 'user-data.json'
$settingsFile = Join-Path $env:TEMP 'user-settings.json'
$userData | ConvertTo-Json | Set-Content -Path $userFile
$settingsData | ConvertTo-Json | Set-Content -Path $settingsFile
# Import all user-*.json files
$allUserData = Import-Json -Path (Join-Path $env:TEMP 'user-*.json')

Copilot uses AI. Check for mistakes.
$allUserData | ForEach-Object {
"Imported from: $($_._SourceFile)"
$_ | Format-List
}

# Example 11: Pipeline usage with Import-Json
'Example 11: Pipeline usage with Import-Json'
$jsonFiles = @($configFile, $userFile, $settingsFile)
$allData = $jsonFiles | Import-Json
"Imported $($allData.Count) JSON files via pipeline"

# Example 12: Error handling with Import-Json
'Example 12: Error handling with Import-Json'
try {
Import-Json -Path '/tmp/nonexistent.json' -ErrorAction Stop
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using hardcoded Unix-style paths like '/tmp/' will fail on Windows systems. Consider using Join-Path $env:TEMP 'nonexistent.json' for cross-platform compatibility.

Suggested change
Import-Json -Path '/tmp/nonexistent.json' -ErrorAction Stop
Import-Json -Path (Join-Path $env:TEMP 'nonexistent.json') -ErrorAction Stop

Copilot uses AI. Check for mistakes.
} catch {
Write-Warning "Caught expected error: $($_.Exception.Message)"
}

# Example 13: Combine Import-Json with Format-Json
'Example 13: Combine Import-Json with Format-Json'
$rawConfig = Import-Json -Path $configFile
$formattedConfig = Format-Json -InputObject $rawConfig -IndentationType Spaces -IndentationSize 2
'Formatted imported configuration:'
$formattedConfig

# Cleanup temporary files
Remove-Item -Path $configFile, $userFile, $settingsFile -ErrorAction SilentlyContinue

#endregion

"`nAll examples completed!"
94 changes: 94 additions & 0 deletions src/functions/public/Import-Json.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition will fail to add the _SourceFile property when JSON represents arrays or primitive types at the root level. For example, a JSON file containing [1,2,3] or "simple string" won't get the source file annotation. Consider handling these cases or documenting this limitation.

Suggested change
Add-Member -InputObject $jsonObject -MemberType NoteProperty -Name '_SourceFile' -Value $resolvedPath.Path -Force
Add-Member -InputObject $jsonObject -MemberType NoteProperty -Name '_SourceFile' -Value $resolvedPath.Path -Force
} elseif ($jsonObject -is [Array] -or $jsonObject -is [string] -or $jsonObject -is [int] -or $jsonObject -is [bool]) {
$jsonObject = [PSCustomObject]@{ Value = $jsonObject; _SourceFile = $resolvedPath.Path }

Copilot uses AI. Check for mistakes.
}

# Output the object
$jsonObject
}
} catch [System.Management.Automation.ItemNotFoundException] {
Write-Error "Path not found: $filePath"
} catch [System.ArgumentException] {
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception type System.ArgumentException is incorrect for JSON parsing errors. ConvertFrom-Json throws System.ArgumentException for parameter issues, but JSON parsing errors typically throw System.Management.Automation.RuntimeException or similar. This catch block may not handle JSON parsing errors as intended.

Suggested change
} catch [System.ArgumentException] {
} catch [System.Management.Automation.RuntimeException] {

Copilot uses AI. Check for mistakes.
Write-Error "Invalid JSON format in file: $filePath. $_"
} catch {
Write-Error "Failed to import JSON from file '$filePath': $_"
}
}
}
}
148 changes: 148 additions & 0 deletions tests/Json.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -495,4 +495,152 @@ Describe 'Module' {
$result | Should -Match '"empty_string":""'
}
}

Context 'Import-Json' {
BeforeAll {
# Create test JSON files
$testDataPath = Join-Path $TestDrive 'testdata'
New-Item -Path $testDataPath -ItemType Directory -Force | Out-Null

$simpleJson = @'
{
"name": "Test User",
"age": 30,
"active": true
}
'@
$simpleJsonPath = Join-Path $testDataPath 'simple.json'
$simpleJson | Out-File -FilePath $simpleJsonPath -Encoding UTF8

$complexJson = @'
{
"users": [
{
"id": 1,
"name": "Alice",
"settings": {
"theme": "dark",
"notifications": true
}
},
{
"id": 2,
"name": "Bob",
"settings": {
"theme": "light",
"notifications": false
}
}
],
"metadata": {
"version": "1.0",
"created": "2023-01-01"
}
}
'@
$complexJsonPath = Join-Path $testDataPath 'complex.json'
$complexJson | Out-File -FilePath $complexJsonPath -Encoding UTF8

$emptyJsonPath = Join-Path $testDataPath 'empty.json'
'' | Out-File -FilePath $emptyJsonPath -Encoding UTF8

$invalidJsonPath = Join-Path $testDataPath 'invalid.json'
'{ invalid json }' | Out-File -FilePath $invalidJsonPath -Encoding UTF8

LogGroup 'Test files created' {
Write-Host "Simple JSON: $simpleJsonPath"
Write-Host "Complex JSON: $complexJsonPath"
Write-Host "Empty JSON: $emptyJsonPath"
Write-Host "Invalid JSON: $invalidJsonPath"
}
}

It 'Should import simple JSON file' {
$result = Import-Json -Path $simpleJsonPath
LogGroup 'simple import result' {
Write-Host "$($result | ConvertTo-Json -Depth 3 -Compress)"
}
$result.name | Should -Be 'Test User'
$result.age | Should -Be 30
$result.active | Should -Be $true
$result._SourceFile | Should -Be $simpleJsonPath
}

It 'Should import complex JSON file' {
$result = Import-Json -Path $complexJsonPath
LogGroup 'complex import result' {
Write-Host "$($result | ConvertTo-Json -Depth 5 -Compress)"
}
$result.users | Should -HaveCount 2
$result.users[0].name | Should -Be 'Alice'
$result.users[1].name | Should -Be 'Bob'
$result.metadata.version | Should -Be '1.0'
$result._SourceFile | Should -Be $complexJsonPath
}

It 'Should import multiple files using wildcards' {
# Test with only valid JSON files to avoid interference from invalid ones
$validJsonPath1 = Join-Path $testDataPath 'valid1.json'
$validJsonPath2 = Join-Path $testDataPath 'valid2.json'

'{"type":"user","name":"Test User"}' | Out-File -FilePath $validJsonPath1 -Encoding UTF8
'{"type":"product","name":"Widget"}' | Out-File -FilePath $validJsonPath2 -Encoding UTF8

$results = Import-Json -Path (Join-Path $testDataPath 'valid*.json')
LogGroup 'wildcard import results' {
Write-Host "Found $($results.Count) results"
$results | ForEach-Object { Write-Host "File: $($_._SourceFile), Type: $($_.type), Name: $($_.name)" }
}
$results | Should -HaveCount 2
($results | Where-Object { $_.name -eq 'Test User' }) | Should -Not -BeNullOrEmpty
($results | Where-Object { $_.name -eq 'Widget' }) | Should -Not -BeNullOrEmpty
}

It 'Should support pipeline input' {
$results = $simpleJsonPath, $complexJsonPath | Import-Json
LogGroup 'pipeline import results' {
Write-Host "Pipeline results count: $($results.Count)"
}
$results | Should -HaveCount 2
($results | Where-Object { $_.name -eq 'Test User' }) | Should -Not -BeNullOrEmpty
($results | Where-Object { $_.users -ne $null }) | Should -Not -BeNullOrEmpty
}

It 'Should handle non-existent file gracefully' {
$nonExistentPath = Join-Path $testDataPath 'nonexistent.json'
{ Import-Json -Path $nonExistentPath -ErrorAction Stop } | Should -Throw
}

It 'Should handle invalid JSON gracefully' {
{ Import-Json -Path $invalidJsonPath -ErrorAction Stop } | Should -Throw
}

It 'Should warn on empty files' {
$warningMessages = @()
Import-Json -Path $emptyJsonPath -WarningVariable warningMessages -WarningAction SilentlyContinue
$warningMessages | Should -Not -BeNullOrEmpty
$warningMessages[0] | Should -Match 'empty or contains only whitespace'
}

It 'Should support custom depth parameter' {
$result = Import-Json -Path $complexJsonPath -Depth 10
$result.users | Should -HaveCount 2
$result.metadata | Should -Not -BeNullOrEmpty
}

It 'Should add source file information' {
$result = Import-Json -Path $simpleJsonPath
$result._SourceFile | Should -Be $simpleJsonPath
}

It 'Should handle relative paths' {
Push-Location $testDataPath
try {
$result = Import-Json -Path 'simple.json'
$result.name | Should -Be 'Test User'
} finally {
Pop-Location
}
}
}
}
Loading