diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs
index 95ef144b43d..3556c1ac39c 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs
@@ -36,6 +36,13 @@ public class ConvertFromJsonCommand : Cmdlet
[Parameter()]
public SwitchParameter AsHashtable { get; set; }
+ ///
+ /// Gets or sets the maximum depth the JSON input is allowed to have. By default, it is 1024.
+ ///
+ [Parameter()]
+ [ValidateRange(ValidateRangeKind.Positive)]
+ public int Depth { get; set; } = 1024;
+
#endregion parameters
#region overrides
@@ -100,7 +107,7 @@ protected override void EndProcessing()
private bool ConvertFromJsonHelper(string input)
{
ErrorRecord error = null;
- object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, out error);
+ object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, Depth, out error);
if (error != null)
{
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 784b8ad0260..a2770b699a2 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs
@@ -19,7 +19,7 @@ namespace Microsoft.PowerShell.Commands
///
/// JsonObject class.
///
- [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")]
public static class JsonObject
{
#region HelperTypes
@@ -112,7 +112,7 @@ private class StoppingException : System.Exception
/// The json text to convert.
/// An error record if the conversion failed.
/// A PSObject.
- [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")]
public static object ConvertFromJson(string input, out ErrorRecord error)
{
return ConvertFromJson(input, returnHashtable: false, out error);
@@ -128,8 +128,25 @@ public static object ConvertFromJson(string input, out ErrorRecord error)
/// An error record if the conversion failed.
/// A or a
/// if the parameter is true.
- [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")]
public static object ConvertFromJson(string input, bool returnHashtable, out ErrorRecord error)
+ {
+ return ConvertFromJson(input, returnHashtable, maxDepth: 1024, out error);
+ }
+
+ ///
+ /// Convert a JSON string back to an object of type or
+ /// depending on parameter .
+ ///
+ /// The JSON text to convert.
+ /// True if the result should be returned as a
+ /// instead of a .
+ /// The max depth allowed when deserializing the json input. Set to null for no maximum.
+ /// An error record if the conversion failed.
+ /// A or a
+ /// if the parameter is true.
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")]
+ public static object ConvertFromJson(string input, bool returnHashtable, int? maxDepth, out ErrorRecord error)
{
if (input == null)
{
@@ -162,7 +179,7 @@ public static object ConvertFromJson(string input, bool returnHashtable, out Err
// This TypeNameHandling setting is required to be secure.
TypeNameHandling = TypeNameHandling.None,
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
- MaxDepth = 1024
+ MaxDepth = maxDepth
});
switch (obj)
@@ -176,7 +193,8 @@ public static object ConvertFromJson(string input, bool returnHashtable, out Err
return returnHashtable
? PopulateHashTableFromJArray(list, out error)
: PopulateFromJArray(list, out error);
- default: return obj;
+ default:
+ return obj;
}
}
catch (JsonException je)
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1
index e4a32a5ee88..8160864dd3c 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1
@@ -1,6 +1,36 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
-Describe 'ConvertFrom-Json' -tags "CI" {
+
+function New-NestedJson {
+ Param(
+ [ValidateRange(1, 2048)]
+ [int]
+ $Depth
+ )
+
+ $nestedJson = "true"
+
+ $Depth..1 | ForEach-Object {
+ $nestedJson = '{"' + $_ + '":' + $nestedJson + '}'
+ }
+
+ return $nestedJson
+}
+
+function Count-ObjectDepth {
+ Param([PSCustomObject] $InputObject)
+
+ for ($i=1; $i -le 2048; $i++)
+ {
+ $InputObject = Select-Object -InputObject $InputObject -ExpandProperty $i
+ if ($InputObject -eq $true)
+ {
+ return $i
+ }
+ }
+}
+
+Describe 'ConvertFrom-Json Unit Tests' -tags "CI" {
BeforeAll {
$testCasesWithAndWithoutAsHashtableSwitch = @(
@@ -9,12 +39,12 @@ Describe 'ConvertFrom-Json' -tags "CI" {
)
}
- It 'Can convert a single-line object with AsHashtable switch set to ' -TestCase $testCasesWithAndWithoutAsHashtableSwitch {
+ It 'Can convert a single-line object with AsHashtable switch set to ' -TestCases $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
('{"a" : "1"}' | ConvertFrom-Json -AsHashtable:$AsHashtable).a | Should -Be 1
}
- It 'Can convert one string-per-object with AsHashtable switch set to ' -TestCase $testCasesWithAndWithoutAsHashtableSwitch {
+ It 'Can convert one string-per-object with AsHashtable switch set to ' -TestCases $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
$json = @('{"a" : "1"}', '{"a" : "x"}') | ConvertFrom-Json -AsHashtable:$AsHashtable
$json.Count | Should -Be 2
@@ -25,7 +55,7 @@ Describe 'ConvertFrom-Json' -tags "CI" {
}
}
- It 'Can convert multi-line object with AsHashtable switch set to ' -TestCase $testCasesWithAndWithoutAsHashtableSwitch {
+ It 'Can convert multi-line object with AsHashtable switch set to ' -TestCases $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
$json = @('{"a" :', '"x"}') | ConvertFrom-Json -AsHashtable:$AsHashtable
$json.a | Should -Be 'x'
@@ -35,7 +65,7 @@ Describe 'ConvertFrom-Json' -tags "CI" {
}
}
- It 'Can convert an object with Newtonsoft.Json metadata properties with AsHashtable switch set to ' -TestCase $testCasesWithAndWithoutAsHashtableSwitch {
+ It 'Can convert an object with Newtonsoft.Json metadata properties with AsHashtable switch set to ' -TestCases $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
$id = 13
$type = 'Calendar.Months.December'
@@ -52,4 +82,86 @@ Describe 'ConvertFrom-Json' -tags "CI" {
$json | Should -BeOfType Hashtable
}
}
+
+ It 'Can convert an object of depth 1024 by default with AsHashtable switch set to ' -TestCases $testCasesWithAndWithoutAsHashtableSwitch {
+ Param($AsHashtable)
+ $nestedJson = New-NestedJson -Depth 1024
+
+ $json = $nestedJson | ConvertFrom-Json -AsHashtable:$AsHashtable
+
+ if ($AsHashtable)
+ {
+ $json | Should -BeOfType Hashtable
+ }
+ else
+ {
+ $json | Should -BeOfType PSCustomObject
+ }
+ }
+
+ It 'Fails to convert an object of depth higher than 1024 by default with AsHashtable switch set to ' -TestCases $testCasesWithAndWithoutAsHashtableSwitch {
+ Param($AsHashtable)
+ $nestedJson = New-NestedJson -Depth 1025
+
+ { $nestedJson | ConvertFrom-Json -AsHashtable:$AsHashtable } |
+ Should -Throw -ErrorId "System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand"
+ }
+}
+
+Describe 'ConvertFrom-Json -Depth Tests' -tags "Feature" {
+
+ BeforeAll {
+ $testCasesJsonDepthWithAndWithoutAsHashtableSwitch = @(
+ @{ Depth = 2; AsHashtable = $true }
+ @{ Depth = 2; AsHashtable = $false }
+ @{ Depth = 200; AsHashtable = $true }
+ @{ Depth = 200; AsHashtable = $false }
+ @{ Depth = 2000; AsHashtable = $true }
+ @{ Depth = 2000; AsHashtable = $false }
+ )
+ }
+
+ It 'Can convert an object with depth less than Depth param set to and AsHashtable switch set to ' -TestCases $testCasesJsonDepthWithAndWithoutAsHashtableSwitch {
+ Param($AsHashtable, $Depth)
+ $nestedJson = New-NestedJson -Depth ($Depth - 1)
+
+ $json = $nestedJson | ConvertFrom-Json -AsHashtable:$AsHashtable -Depth $Depth
+
+ if ($AsHashtable)
+ {
+ $json | Should -BeOfType Hashtable
+ }
+ else
+ {
+ $json | Should -BeOfType PSCustomObject
+ }
+
+ (Count-ObjectDepth -InputObject $json) | Should -Be ($Depth - 1)
+ }
+
+ It 'Can convert an object with depth equal to Depth param set to and AsHashtable switch set to ' -TestCases $testCasesJsonDepthWithAndWithoutAsHashtableSwitch {
+ Param($AsHashtable, $Depth)
+ $nestedJson = New-NestedJson -Depth:$Depth
+
+ $json = $nestedJson | ConvertFrom-Json -AsHashtable:$AsHashtable -Depth $Depth
+
+ if ($AsHashtable)
+ {
+ $json | Should -BeOfType Hashtable
+ }
+ else
+ {
+ $json | Should -BeOfType PSCustomObject
+ }
+
+ (Count-ObjectDepth -InputObject $json) | Should -Be $Depth
+ }
+
+ It 'Fails to convert an object with greater depth than Depth param set to and AsHashtable switch set to ' -TestCases $testCasesJsonDepthWithAndWithoutAsHashtableSwitch {
+ Param($AsHashtable, $Depth)
+ $nestedJson = New-NestedJson -Depth ($Depth + 1)
+
+ { $nestedJson | ConvertFrom-Json -AsHashtable:$AsHashtable -Depth $Depth } |
+ Should -Throw -ErrorId "System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand"
+ }
}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/JsonObject.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/JsonObject.Tests.ps1
index a23d404bb1b..2c61abd2915 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/JsonObject.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/JsonObject.Tests.ps1
@@ -5,9 +5,34 @@ Describe 'Unit tests for JsonObject' -tags "CI" {
BeforeAll {
$jsonWithEmptyKey = '{"": "Value"}'
$jsonContainingKeysWithDifferentCasing = '{"key1": "Value1", "Key1": "Value2"}'
+
+ $testCasesJsonDepthWithAndWithoutReturnHashTable = @(
+ @{ Depth = 2; ReturnHashTable = $true }
+ @{ Depth = 2; ReturnHashTable = $false }
+ @{ Depth = 200; ReturnHashTable = $true }
+ @{ Depth = 200; ReturnHashTable = $false }
+ @{ Depth = 2000; ReturnHashTable = $true }
+ @{ Depth = 2000; ReturnHashTable = $false }
+ )
+
+ function New-NestedJson {
+ Param(
+ [ValidateRange(1, 2048)]
+ [int]
+ $Depth
+ )
+
+ $nestedJson = "true"
+
+ $Depth..1 | ForEach-Object {
+ $nestedJson = '{"' + $_ + '":' + $nestedJson + '}'
+ }
+
+ return $nestedJson
+ }
}
- It 'No error for valid string '''' with -ReturnHashTable:$' -TestCase @(
+ It 'No error for valid string '''' with -ReturnHashTable:' -TestCases @(
@{ name = "null"; str = $null; ReturnHashTable = $true }
@{ name = "empty"; str = ""; ReturnHashTable = $true }
@{ name = "spaces"; str = " "; ReturnHashTable = $true }
@@ -23,7 +48,7 @@ Describe 'Unit tests for JsonObject' -tags "CI" {
$errRecord | Should -BeNullOrEmpty
}
- It 'Throw ArgumentException for invalid string '''' with -ReturnHashTable:$' -TestCase @(
+ It 'Throw ArgumentException for invalid string '''' with -ReturnHashTable:' -TestCases @(
@{ name = "plain text"; str = "plaintext"; ReturnHashTable = $true }
@{ name = "part"; str = '{"a" :'; ReturnHashTable = $true }
@{ name = "plain text"; str = "plaintext"; ReturnHashTable = $false }
@@ -34,7 +59,31 @@ Describe 'Unit tests for JsonObject' -tags "CI" {
{ [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($str, $ReturnHashTable, [ref]$errRecord) } | Should -Throw -ErrorId "ArgumentException"
}
- Context 'Empty key name' {
+ It 'Can Convert json with depth less than -Depth: with -ReturnHashTable:' -TestCases $testCasesJsonDepthWithAndWithoutReturnHashTable {
+ param ($Depth, $ReturnHashTable)
+ $errRecord = $null
+ $json = New-NestedJson -Depth ($Depth - 1)
+ [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($json, $ReturnHashTable, $Depth, [ref]$errRecord)
+ $errRecord | Should -BeNullOrEmpty
+ }
+
+ It 'Can Convert json with depth equal to -Depth: with -ReturnHashTable:' -TestCases $testCasesJsonDepthWithAndWithoutReturnHashTable {
+ param ($Depth, $ReturnHashTable)
+ $errRecord = $null
+ $json = New-NestedJson -Depth $Depth
+ [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($json, $ReturnHashTable, $Depth, [ref]$errRecord)
+ $errRecord | Should -BeNullOrEmpty
+ }
+
+ It 'Throws ArgumentException for json with greater depth than -Depth: with -ReturnHashTable:' -TestCases $testCasesJsonDepthWithAndWithoutReturnHashTable {
+ param ($Depth, $ReturnHashTable)
+ $errRecord = $null
+ $json = New-NestedJson -Depth ($Depth + 1)
+ { [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($json, $ReturnHashTable, $Depth, [ref]$errRecord) } |
+ Should -Throw -ErrorId "ArgumentException"
+ }
+
+ Context 'Empty key name' {
It 'Throw InvalidOperationException when json contains empty key name' {
$errorRecord = $null
[Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($jsonWithEmptyKey, [ref]$errorRecord)
@@ -51,7 +100,7 @@ Describe 'Unit tests for JsonObject' -tags "CI" {
}
Context 'Keys with different casing ' {
-
+
It 'Throw InvalidOperationException when json contains key with different casing' {
$errorRecord = $null
[Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($jsonContainingKeysWithDifferentCasing, [ref]$errorRecord)