From 3e3d83cfa4159bc693bdb51c85b8f8091dd0584f Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 20 Sep 2024 03:24:20 +1000 Subject: [PATCH] Treat large Enum values as numbers in `ConvertTo-Json` (#20999) --- experimental-feature-linux.json | 1 + experimental-feature-windows.json | 1 + .../commands/utility/WebCmdlet/JsonObject.cs | 2 +- .../ExperimentalFeature.cs | 5 +++ ....PSSerializeJSONLongEnumAsNumber.Tests.ps1 | 34 +++++++++++++++++ .../Json.Tests.ps1 | 38 ++++++++++--------- test/tools/TestMetadata.json | 3 +- 7 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.PSSerializeJSONLongEnumAsNumber.Tests.ps1 diff --git a/experimental-feature-linux.json b/experimental-feature-linux.json index f97b56b601..ca5b49878a 100644 --- a/experimental-feature-linux.json +++ b/experimental-feature-linux.json @@ -2,6 +2,7 @@ "PSFeedbackProvider", "PSLoadAssemblyFromNativeCode", "PSNativeWindowsTildeExpansion", + "PSSerializeJSONLongEnumAsNumber", "PSRedirectToVariable", "PSSubsystemPluginModel" ] diff --git a/experimental-feature-windows.json b/experimental-feature-windows.json index f97b56b601..ca5b49878a 100644 --- a/experimental-feature-windows.json +++ b/experimental-feature-windows.json @@ -2,6 +2,7 @@ "PSFeedbackProvider", "PSLoadAssemblyFromNativeCode", "PSNativeWindowsTildeExpansion", + "PSSerializeJSONLongEnumAsNumber", "PSRedirectToVariable", "PSSubsystemPluginModel" ] diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs index 8e0e7e7776..3346568315 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs @@ -577,7 +577,7 @@ namespace Microsoft.PowerShell.Commands { Type t = obj.GetType(); - if (t.IsPrimitive) + if (t.IsPrimitive || (t.IsEnum && ExperimentalFeature.IsEnabled(ExperimentalFeature.PSSerializeJSONLongEnumAsNumber))) { rv = obj; } diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 7e358e94e9..dd26e60964 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -24,6 +24,7 @@ namespace System.Management.Automation internal const string PSFeedbackProvider = "PSFeedbackProvider"; internal const string PSNativeWindowsTildeExpansion = nameof(PSNativeWindowsTildeExpansion); internal const string PSRedirectToVariable = "PSRedirectToVariable"; + internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber); #endregion @@ -121,6 +122,10 @@ namespace System.Management.Automation new ExperimentalFeature( name: PSRedirectToVariable, description: "Add support for redirecting to the variable drive"), + new ExperimentalFeature( + name: PSSerializeJSONLongEnumAsNumber, + description: "Serialize enums based on long or ulong as an numeric value rather than the string representation when using ConvertTo-Json." + ) }; EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.PSSerializeJSONLongEnumAsNumber.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.PSSerializeJSONLongEnumAsNumber.Tests.ps1 new file mode 100644 index 0000000000..d21b87ce22 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.PSSerializeJSONLongEnumAsNumber.Tests.ps1 @@ -0,0 +1,34 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Describe 'ConvertTo-Json with PSSerializeJSONLongEnumAsNumber' -tags "CI" { + + BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues['It:Skip'] = -not [ExperimentalFeature]::IsEnabled('PSSerializeJSONLongEnumAsNumber') + } + + AfterAll { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + + It 'Should treat enums as integers' { + enum LongEnum : long { + LongValue = -1 + } + + enum ULongEnum : ulong { + ULongValue = 18446744073709551615 + } + + $obj = [Ordered]@{ + Long = [LongEnum]::LongValue + ULong = [ULongEnum]::ULongValue + } + + $actual = ConvertTo-Json -InputObject $obj -Compress + $actual | Should -Be '{"Long":-1,"ULong":18446744073709551615}' + + $actual = ConvertTo-Json -InputObject $obj -EnumsAsStrings -Compress + $actual | Should -Be '{"Long":"LongValue","ULong":"ULongValue"}' + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Json.Tests.ps1 index 5f33e1b6b7..46ce42c223 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Json.Tests.ps1 @@ -58,7 +58,9 @@ Describe "Json Tests" -Tags "Feature" { $valueFromNotCompressedResult.FirstName | Should -Match $valueFromCompressedResult.FirstName } - It "Convertto-Json should handle Enum based on Int64" { + It "Convertto-Json should handle Enum based on Int64" -Skip:( + [ExperimentalFeature]::IsEnabled("PSSerializeJSONLongEnumAsNumber") + ) { # Test follow-up for bug Win8: 378368 Convertto-Json problems with Enum based on Int64. if ( $null -eq ("JsonEnumTest" -as "Type")) { @@ -355,7 +357,7 @@ Describe "Json Tests" -Tags "Feature" { { "date-s-should-parse-as-datetime": "2008-09-22T14:01:54", "date-upperO-should-parse-as-datetime": "2008-09-22T14:01:54.9571247Z", - + "date-o-should-parse-as-string": "2019-12-17T06:14:06 +06:00", "date-upperD-should-parse-as-string": "Monday, September 22, 2008", "date-f-should-parse-as-string": "Monday, September 22, 2008 2:01 PM", @@ -399,7 +401,7 @@ Describe "Json Tests" -Tags "Feature" { $result."date-s-should-parse-as-datetime".ToString("Y") | Should -Be "September 2008" $result."date-s-should-parse-as-datetime".ToString("y") | Should -Be "September 2008" $result."date-s-should-parse-as-datetime" | Should -BeOfType [DateTime] - + $result."date-upperO-should-parse-as-datetime" = [datetime]::SpecifyKind($result."date-upperO-should-parse-as-datetime", [System.DateTimeKind]::Utc) $result."date-upperO-should-parse-as-datetime".ToString("d") | Should -Be "9/22/2008" $result."date-upperO-should-parse-as-datetime".ToString("D") | Should -Be "Monday, September 22, 2008" @@ -420,7 +422,7 @@ Describe "Json Tests" -Tags "Feature" { $result."date-upperO-should-parse-as-datetime".ToString("Y") | Should -Be "September 2008" $result."date-upperO-should-parse-as-datetime".ToString("y") | Should -Be "September 2008" $result."date-upperO-should-parse-as-datetime" | Should -BeOfType [DateTime] - + $result."date-o-should-parse-as-string" | Should -Be "2019-12-17T06:14:06 +06:00" $result."date-o-should-parse-as-string" | Should -BeOfType [String] $result."date-f-should-parse-as-string" | Should -Be "Monday, September 22, 2008 2:01 PM" @@ -453,7 +455,7 @@ Describe "Json Tests" -Tags "Feature" { $result."date-y-should-parse-as-string" | Should -BeOfType [String] } } - + It "ConvertFrom-Json properly parses complex objects" { $json = @" { @@ -541,13 +543,13 @@ Describe "Json Tests" -Tags "Feature" { $result."registered" | Should -BeOfType [String] $result."_id"| Should -BeExactly "60dd3ea9253016932039a0a2" $result."_id" | Should -BeOfType [String] - + $result.Tags | Should -BeOfType [string] - - $result.Tags.count | Should -Be 7 + + $result.Tags.count | Should -Be 7 $result.Tags[0] | Should -BeExactly "laboris" $result.Tags | Should -Be @("laboris", "voluptate", "amet", "ad", "velit", "ipsum", "do") - + $result.Friends | Should -BeOfType [pscustomobject] $result.Friends[0].id | Should -Be 0 $result.Friends[0].name | Should -BeExactly "Renee Holden" @@ -556,7 +558,7 @@ Describe "Json Tests" -Tags "Feature" { $result.Friends[2].id | Should -Be 2 $result.Friends[2].name | Should -BeExactly "Emilia Holder" } - + It "ConvertFrom-Json chooses the appropriate number type" { ConvertFrom-Json -InputObject "5" | should -Be 5 ConvertFrom-Json -InputObject 5 | should -Be 5 @@ -570,33 +572,33 @@ Describe "Json Tests" -Tags "Feature" { ConvertFrom-Json -InputObject 5.0 | should -Be 5.0 ConvertFrom-Json -InputObject "5.0" | should -BeOfType [double] ConvertFrom-Json -InputObject 5.0 | should -BeOfType [double] - + # The decimal is lost but only when this is quoted ConvertFrom-Json -InputObject "500000000000.0000000000000001" | should -Be "500000000000" - + # Counter intuitively all four of these tests pass because precision is lost on both sides of the test, likely due to powershell number handling ConvertFrom-Json -InputObject 500000000000.0000000000000001 | should -Be 500000000000 ConvertFrom-Json -InputObject 500000000000.0000000000000001 | should -Be 500000000000.0000000000000001 ConvertFrom-Json -InputObject 500000000000 | should -Be 500000000000.0000000000000001 ConvertFrom-Json -InputObject 500000000000 | should -Be 500000000000 - + ConvertFrom-Json -InputObject "500000000000.0000000000000001" | should -BeOfType [double] ConvertFrom-Json -InputObject 500000000000.0000000000000001 | should -BeOfType [double] - + # these tests also pass because precision is lost during conversion/powershell handling ConvertFrom-Json -InputObject "50000000000000000000000000000000000.0000000000000001" | should -Be "5E+34" ConvertFrom-Json -InputObject 50000000000000000000000000000000000.0000000000000001 | should -Be "5E+34" - + ConvertFrom-Json -InputObject "50000000000000000000000000000000000.0000000000000001" | should -BeOfType [double] ConvertFrom-Json -InputObject 50000000000000000000000000000000000.0000000000000001 | should -BeOfType [double] - - + + ConvertFrom-Json -InputObject "50000000000000000000000000000000000" | should -Be 50000000000000000000000000000000000 ConvertFrom-Json -InputObject 50000000000000000000000000000000000 | should -Be 50000000000000000000000000000000000 ConvertFrom-Json -InputObject "50000000000000000000000000000000000" | should -BeOfType [BigInt] ConvertFrom-Json -InputObject 50000000000000000000000000000000000 | should -BeOfType [BigInt] } - + It "ConvertFrom-Json with special characters" { $json = '{"SampleValue":"\"\\\b\f\n\r\t\u4321\uD7FF"}' diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json index cd94ce83a7..bd716ccec7 100644 --- a/test/tools/TestMetadata.json +++ b/test/tools/TestMetadata.json @@ -3,6 +3,7 @@ "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ], "PSCultureInvariantReplaceOperator": [ "test/powershell/Language/Operators/ReplaceOperator.Tests.ps1" ], "Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/RunspaceBreakpointManagement.Tests.ps1" ], - "PSNativeWindowsTildeExpansion": [ "test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1" ] + "PSNativeWindowsTildeExpansion": [ "test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1" ], + "PSSerializeJSONLongEnumAsNumber": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.PSSerializeJSONLongEnumAsNumber.Tests.ps1" ] } }