Skip to content

Commit 6cf344f

Browse files
Prevent ConvertTo-SmarterObject from flattening arrays (#56)
* Ensure that `ConvertTo-SmarterObject` does not cause any side-effects on `InputObject` * Ensure that `ConvertTo-SmarterObject` does not flatten any arrays * Switched all calls of `ConvertTo-Json` to not use pipelining in order to avoid PowerShell's array-flattening logic when piping the InputObject in, as opposed to when passing it as a parameter. * Ensured that we do best-effort Date conversion in `ConvertTo-SmarterObject` (a failed date conversion should never cause an exception/failure). * Used a workaround described in [Pester's Wiki](# https://github.com/pester/Pester/wiki/Testing-different-module-types) to force the module to be a `Script Module` instead of a `Manifest Module` so that `Mock` and `InModuleScope` (which lets you test private methods) work. * Added UT's for `ConvertTo-SmarterObject` core scenarios. Resolves PowerShell#55: ConvertTo-SmarterObject is flattening arrays Thanks to @danbelcher-MSFT for the assist on this one.
1 parent 35b06ce commit 6cf344f

11 files changed

+315
-28
lines changed

GitHubConfiguration.ps1

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,7 @@ function Save-GitHubConfiguration
309309
)
310310

311311
$null = New-Item -Path $Path -Force
312-
$Configuration |
313-
ConvertTo-Json |
312+
ConvertTo-Json -InputObject $Configuration |
314313
Set-Content -Path $Path -Force -ErrorAction SilentlyContinue -ErrorVariable ev
315314

316315
if (($null -ne $ev) -and ($ev.Count -gt 0))
@@ -654,7 +653,7 @@ function Backup-GitHubConfiguration
654653
}
655654
else
656655
{
657-
@{} | ConvertTo-Json | Set-Content -Path $Path -Force:$Force
656+
ConvertTo-Json -InputObject @{} | Set-Content -Path $Path -Force:$Force
658657
}
659658
}
660659

GitHubCore.ps1

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ function Invoke-GHRestMethod
261261
Write-Log -Message "Unable to retrieve the raw HTTP Web Response:" -Exception $_ -Level Warning
262262
}
263263

264-
throw ($ex | ConvertTo-Json -Depth 20)
264+
throw (ConvertTo-Json -InputObject $ex -Depth 20)
265265
}
266266
}
267267

@@ -840,8 +840,12 @@ filter ConvertTo-SmarterObject
840840
.PARAMETER InputObject
841841
The object to update
842842
#>
843+
[CmdletBinding()]
843844
param(
844-
[Parameter(Mandatory)]
845+
[Parameter(
846+
Mandatory,
847+
ValueFromPipeline,
848+
ValueFromPipelineByPropertyName)]
845849
[AllowNull()]
846850
[object] $InputObject
847851
)
@@ -851,31 +855,44 @@ filter ConvertTo-SmarterObject
851855
return $null
852856
}
853857

854-
if ($InputObject -is [array])
858+
if ($InputObject -is [System.Collections.IList])
855859
{
856-
foreach ($object in $InputObject)
857-
{
858-
Write-Output -InputObject (ConvertTo-SmarterObject -InputObject $object)
859-
}
860+
$InputObject |
861+
ConvertTo-SmarterObject |
862+
Write-Output
860863
}
861864
elseif ($InputObject -is [PSCustomObject])
862865
{
863-
$properties = $InputObject.PSObject.Properties | Where-Object { $null -ne $_.Value }
866+
$clone = DeepCopy-Object -InputObject $InputObject
867+
$properties = $clone.PSObject.Properties | Where-Object { $null -ne $_.Value }
864868
foreach ($property in $properties)
865869
{
866870
# Convert known date properties from dates to real DateTime objects
867-
if ($property.Name -in $script:datePropertyNames)
871+
if (($property.Name -in $script:datePropertyNames) -and
872+
($property.Value -is [String]) -and
873+
(-not [String]::IsNullOrWhiteSpace($property.Value)))
868874
{
869-
$property.Value = Get-Date -Date $property.Value
875+
try
876+
{
877+
$property.Value = Get-Date -Date $property.Value
878+
}
879+
catch
880+
{
881+
Write-Log -Message "Unable to convert $($property.Name) value of $($property.Value) to a [DateTime] object. Leaving as-is." -Level Verbose
882+
}
870883
}
871884

872-
if (($property.Value -is [array]) -or ($property.Value -is [PSCustomObject]))
885+
if ($property.Value -is [System.Collections.IList])
886+
{
887+
$property.Value = @(ConvertTo-SmarterObject -InputObject $property.Value)
888+
}
889+
elseif ($property.Value -is [PSCustomObject])
873890
{
874891
$property.Value = ConvertTo-SmarterObject -InputObject $property.Value
875892
}
876893
}
877894

878-
Write-Output -InputObject $InputObject
895+
Write-Output -InputObject $clone
879896
}
880897
else
881898
{

GitHubIssues.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ function New-GitHubIssue
514514

515515
$params = @{
516516
'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues"
517-
'Body' = ($hashBody | ConvertTo-Json)
517+
'Body' = (ConvertTo-Json -InputObject $hashBody)
518518
'Method' = 'Post'
519519
'Description' = "Creating new Issue ""$Title"" on $RepositoryName"
520520
'AcceptHeader' = 'application/vnd.github.symmetra-preview+json'
@@ -654,7 +654,7 @@ function Update-GitHubIssue
654654

655655
$params = @{
656656
'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue"
657-
'Body' = ($hashBody | ConvertTo-Json)
657+
'Body' = (ConvertTo-Json -InputObject $hashBody)
658658
'Method' = 'Patch'
659659
'Description' = "Updating Issue #$Issue on $RepositoryName"
660660
'AcceptHeader' = 'application/vnd.github.symmetra-preview+json'
@@ -760,7 +760,7 @@ function Lock-GitHubIssue
760760

761761
$params = @{
762762
'UriFragment' = "/repos/$OwnerName/$RepositoryName/issues/$Issue/lock"
763-
'Body' = ($hashBody | ConvertTo-Json)
763+
'Body' = (ConvertTo-Json -InputObject $hashBody)
764764
'Method' = 'Put'
765765
'Description' = "Locking Issue #$Issue on $RepositoryName"
766766
'AcceptHeader' = 'application/vnd.github.sailor-v-preview+json'

GitHubLabels.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ function New-GitHubLabel
210210

211211
$params = @{
212212
'UriFragment' = "repos/$OwnerName/$RepositoryName/labels"
213-
'Body' = ($hashBody | ConvertTo-Json)
213+
'Body' = (ConvertTo-Json -InputObject $hashBody)
214214
'Method' = 'Post'
215215
'Description' = "Creating label $Name in $RepositoryName"
216216
'AcceptHeader' = 'application/vnd.github.symmetra-preview+json'
@@ -425,7 +425,7 @@ function Update-GitHubLabel
425425

426426
$params = @{
427427
'UriFragment' = "repos/$OwnerName/$RepositoryName/labels/$Name"
428-
'Body' = ($hashBody | ConvertTo-Json)
428+
'Body' = (ConvertTo-Json -InputObject $hashBody)
429429
'Method' = 'Patch'
430430
'Description' = "Updating label $Name"
431431
'AcceptHeader' = 'application/vnd.github.symmetra-preview+json'

GitHubMiscellaneous.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ function ConvertFrom-Markdown
150150

151151
$params = @{
152152
'UriFragment' = 'markdown'
153-
'Body' = ($hashBody | ConvertTo-Json)
153+
'Body' = (ConvertTo-Json -InputObject $hashBody)
154154
'Method' = 'Post'
155155
'Description' = "Converting Markdown to HTML"
156156
'AccessToken' = $AccessToken

GitHubRepositories.ps1

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ function New-GitHubRepository
166166

167167
$params = @{
168168
'UriFragment' = $uriFragment
169-
'Body' = ($hashBody | ConvertTo-Json)
169+
'Body' = (ConvertTo-Json -InputObject $hashBody)
170170
'Method' = 'Post'
171171
'Description' = "Creating $RepositoryName"
172172
'AccessToken' = $AccessToken
@@ -609,7 +609,7 @@ function Update-GitHubRepository
609609

610610
$params = @{
611611
'UriFragment' = "repos/$OwnerName/$ReposistoryName"
612-
'Body' = ($hashBody | ConvertTo-Json)
612+
'Body' = (ConvertTo-Json -InputObject $hashBody)
613613
'Method' = 'Patch'
614614
'Description' = "Updating $RepositoryName"
615615
'AccessToken' = $AccessToken
@@ -817,7 +817,7 @@ function Set-GitHubRepositoryTopic
817817

818818
$params = @{
819819
'UriFragment' = "repos/$OwnerName/$RepositoryName/topics"
820-
'Body' = ($hashBody | ConvertTo-Json)
820+
'Body' = (ConvertTo-Json -InputObject $hashBody)
821821
'Method' = 'Put'
822822
'Description' = $description
823823
'AcceptHeader' = 'application/vnd.github.mercy-preview+json'
@@ -1299,7 +1299,7 @@ function Move-GitHubRepositoryOwnership
12991299

13001300
$params = @{
13011301
'UriFragment' = "repos/$OwnerName/$RepositoryName/transfer"
1302-
'Body' = ($hashBody | ConvertTo-Json)
1302+
'Body' = (ConvertTo-Json -InputObject $hashBody)
13031303
'Method' = 'Post'
13041304
'Description' = "Transferring ownership of $RepositoryName to $NewOwnerName"
13051305
'AccessToken' = $AccessToken

GitHubUsers.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ function Update-GitHubCurrentUser
259259
$params = @{
260260
'UriFragment' = 'user'
261261
'Method' = 'Patch'
262-
'Body' = ($hashBody | ConvertTo-Json)
262+
'Body' = (ConvertTo-Json -InputObject $hashBody)
263263
'Description' = "Updating current authenticated user"
264264
'AccessToken' = $AccessToken
265265
'TelemetryEventName' = $MyInvocation.MyCommand.Name

Helpers.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ function Write-InvocationLog
467467
}
468468
else
469469
{
470-
$params += "-$($param.Key) $($param.Value | ConvertTo-Json -Depth $jsonConversionDepth -Compress)"
470+
$params += "-$($param.Key) $(ConvertTo-Json -InputObject $param.Value -Depth $jsonConversionDepth -Compress)"
471471
}
472472
}
473473
}

PowerShellForGitHub.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
Description = 'PowerShell wrapper for GitHub API'
1212

1313
# Script module or binary module file associated with this manifest.
14-
# RootModule = 'GitHubCore.psm1'
14+
RootModule = 'PowerShellForGitHub.psm1'
1515

1616
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
1717
NestedModules = @(

PowerShellForGitHub.psm1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# This file only exists to enable making this a "Script" module instead of a "Manifest" module
2+
# so that Pester tests are able to use Pester's Mock and InModuleScope features for internal
3+
# methods. For more information, refer to:
4+
# https://github.com/pester/Pester/wiki/Testing-different-module-types

0 commit comments

Comments
 (0)