Add -AsHashtable switch to ConvertFrom-Json for issue #3623#5043
Add -AsHashtable switch to ConvertFrom-Json for issue #3623#5043TravisEz13 merged 13 commits intoPowerShell:masterfrom
Conversation
iSazonov
left a comment
There was a problem hiding this comment.
@bergmeister Thanks for great contribution!
| /// <returns>A PSObject or a Hashtable if the <paramref name="returnHashTable"/> parameter is true.</returns> | ||
| [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] | ||
| public static object ConvertFromJson(string input, out ErrorRecord error) | ||
| public static object ConvertFromJson(string input, bool returnHashTable, out ErrorRecord error) |
There was a problem hiding this comment.
We shouldn't change a public API. Please add new overload.
There was a problem hiding this comment.
Thanks. I did not know PowerShell also exposes some of its classes. I'll re-add the old signature API that will then internally call the new overload.
| @{ name = "empty"; str = ""; ReturnHashTable = $false } | ||
| @{ name = "spaces"; str = " "; ReturnHashTable = $false } | ||
| @{ name = "object"; str = "{a:1}"; ReturnHashTable = $false } | ||
| ) |
There was a problem hiding this comment.
Please move to BeforeAll block.
There was a problem hiding this comment.
I am not sure what you exactly mean by that. I removed the redundant line about `$TestCasesForReturnHashTableParameter that was a leftover.
There was a problem hiding this comment.
Please see sample
We should place $TestCasesForReturnHashTableParameter and $validStrings in the BeforeAll block.
|
|
||
| It 'no error for valid string - <name>' -TestCase $validStrings { | ||
| param ($str) | ||
| It 'no error for valid string - <name> when ReturnHashTable is <ReturnHashTable>' -TestCase $validStrings { |
There was a problem hiding this comment.
Maybe 'no error for valid string - with -ReturnHashTable:`$<ReturnHashTable>'
There was a problem hiding this comment.
I changed to match your suggestion whilst still displaying the name of the string.
| /// <summary> | ||
| /// Returned data structure is a Hashtable instead a CustomPSObject. | ||
| /// </summary> | ||
| [Parameter(Mandatory = false)] |
There was a problem hiding this comment.
It is default. Please remove.
There was a problem hiding this comment.
What do you mean by that? Can you elaborate please (what is 'it')?
There was a problem hiding this comment.
[Parameter(Mandatory = false)] is default
There was a problem hiding this comment.
Ok. I see, thanks.
| { | ||
| obj = PopulateFromJArray(list, out error); | ||
| } | ||
| } |
There was a problem hiding this comment.
Please add 'else' with Diagnostics.Assert (if the object is not JObject or JArray).
There was a problem hiding this comment.
Because of the cast to JArray a few lines above, obj cannot be something else. I initially put your suggestion in but even the compiler tells me that:
error CS0184: The given expression is never of the provided ('JObject') type
There was a problem hiding this comment.
I meant:
Diagnostics.Assert(
false,
"Only JObject or JArray is supported.");…minor suggestions in test naming
…tch since the parameter is not mandatory by default
…ld have been only the Mandatory=false part that should be removed.
| { | ||
| ErrorRecord error; | ||
| obj = JsonObject.ConvertFromJson(json, out error); | ||
| obj = JsonObject.ConvertFromJson(json, false, out error); |
There was a problem hiding this comment.
Please revert the change - we have the overload.
There was a problem hiding this comment.
Yes, you're right.
| @{ name = "plain text"; str = "plaintext"; ReturnHashTable = $true } | ||
| @{ name = "part"; str = '{"a" :'; ReturnHashTable = $true } | ||
| @{ name = "plain text"; str = "plaintext"; ReturnHashTable = $false } | ||
| @{ name = "part"; str = '{"a" :'; ReturnHashTable = $false } |
There was a problem hiding this comment.
I see we have a check for input == null but haven't the test - please add new test with str = $null.
…that is not necessary any more since the original method signature has been restored. -Improve test to add case for $null
|
|
|
We have discussion for this #3623 |
|
@dantraMSFT Can you review this? |
|
As part of this PR, if we can detect that the json has an property named empty string for the pscustomobject path and provide an error message like: The provided json includes a property who name is an empty string, this is only supported using the `-AsHashTable` switch.Then we can resolve #1755 as fixed. |
… -AsHashTable via error message. Fixed a typo that I found in the message strings.
| <value>Path '{0}' resolves to a directory. Specify a path including a file name, and then retry the command.</value> | ||
| </data> | ||
| <data name="EmptyKeyInJsonString" xml:space="preserve"> | ||
| <value>The provided json includes a property whose name is an empty string, this is only supported using the -AsHashTable switch.</value> |
There was a problem hiding this comment.
Should probably capitalize json as JSON
|
|
||
| It 'no error for valid string - <name>' -TestCase $validStrings { | ||
| param ($str) | ||
| It 'no error for valid string ''<name>'' with -ReturnHashTable:$<ReturnHashTable>' -TestCase $validStrings { |
There was a problem hiding this comment.
Please start test descriptions with capital.
We use $validStrings one time. Please use our common template:
It 'no error for valid string ''<name>'' with -ReturnHashTable:$<ReturnHashTable>' -TestCase @(
@{ name = "null"; str = $null; ReturnHashTable = $true }
...
) {
...
}|
|
||
| It 'throw ArgumentException for invalid string - <name>' -TestCase $invalidStrings { | ||
| param ($str) | ||
| It 'throw ArgumentException for invalid string ''<name>'' with -ReturnHashTable:$<ReturnHashTable>' -TestCase $invalidStrings { |
| { [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($str, $ReturnHashTable, [ref]$errRecord) } | ShouldBeErrorId "ArgumentException" | ||
| } | ||
|
|
||
| $jsonWithEmptyKey = '{"": "Value"}' |
There was a problem hiding this comment.
We should put variables in BeforeAll block or in It block.
| It 'throw InvalidOperationException when json contains empty key name' { | ||
| $errorRecord = $null | ||
| [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($jsonWithEmptyKey, [ref]$errorRecord) | ||
| $errorRecord.Exception.GetType() | Should Be System.InvalidOperationException |
There was a problem hiding this comment.
Usually we don't check an exception types. Please remove.
(For type check we use Should BeOfType)
| $errorRecord = $null | ||
| [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($jsonWithEmptyKey, [ref]$errorRecord) | ||
| $errorRecord.Exception.GetType() | Should Be System.InvalidOperationException | ||
| $errorRecord.FullyQualifiedErrorId | Should Be EmptyKeyInJsonString |
There was a problem hiding this comment.
Please put EmptyKeyInJsonString in quotas. It is more readable.
| It 'can convert a single-line object' { | ||
| ('{"a" : "1"}' | ConvertFrom-Json).a | Should Be 1 | ||
|
|
||
| $testCasesWithAndWithoutAsHashtableSwitch = @( |
There was a problem hiding this comment.
Please put this in BeforeAll block.
| @{ AsHashtable = $false } | ||
| ) | ||
|
|
||
| It 'can convert a single-line object with AsHashtable switch set to <AsHashtable>' -TestCase $testCasesWithAndWithoutAsHashtableSwitch { |
There was a problem hiding this comment.
Please start test descriptions with capital.
| $jsonWithEmptyKey = '{"": "Value"}' | ||
| } | ||
|
|
||
|
|
There was a problem hiding this comment.
Please remove extra line.
|
@SteveL-MSFT @TravisEz13 @dantraMSFT Could you please review? Also should we address the comment from @powercode
|
Removed extra newline in pester test as requested by @iSazonov
|
@bergmeister Thanks for your contribution! LGTM with above question about case sensitivity. |
|
@iSazonov @powercode regarding case sensitivity, I think we should treat it the same as the empty key. [Hashtable] allows two keys differing in case, so if we detect that, we should just tell the user to use |
…found out that newtonsoft.json would squash keys that have the same casing into one and just use the last value. I still handle this case should the behaviour change.
|
@SteveL-MSFT @iSazonov @powercode I added a specific error message to point the user to the new |
There was a problem hiding this comment.
@bergmeister Thanks for your contribution! I believe we are near to merge.
Feel free to open new Issue to discuss the discovered problem.
| <value>Cannot convert the JSON string because a dictionary that was converted from the string contains the duplicated key '{0}'.</value> | ||
| </data> | ||
| <data name="KeysWithDifferentCasingInJsonString" xml:space="preserve"> | ||
| <value>Cannot convert the JSON string because a PSObject does not support keys with different casing. Please use the -AsHashTable switch instead. The key that was attempted to be added to the exisiting key '{0}' was '{1}'.</value> |
There was a problem hiding this comment.
I know. I shortened it a bit but I think the last sentence still adds value.
| It 'Not throw when json contains key (same casing) when ReturnHashTable is true' { | ||
| $errorRecord = $null | ||
| $result = [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($jsonContainingKeysWithDifferentCasing, $true, [ref]$errorRecord) | ||
| $result | Should Not Be $null |
There was a problem hiding this comment.
I think it makes sense to check the hashtable values.
| param ($str, $ReturnHashTable) | ||
| $errRecord = $null | ||
| [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($str, [ref]$errRecord) | ||
| [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($str, $ReturnHashTable, [ref]$errRecord) |
There was a problem hiding this comment.
Please validate the returned object
There was a problem hiding this comment.
You should capture the output as $result and validate it is what you expect
| It 'Not throw when json contains empty key name when ReturnHashTable is true' { | ||
| $errorRecord = $null | ||
| $result = [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($jsonWithEmptyKey, $true, [ref]$errorRecord) | ||
| $result | Should Not Be $null |
There was a problem hiding this comment.
Please validate the returned object
| It 'Not throw when json contains key (same casing) when ReturnHashTable is true' { | ||
| $errorRecord = $null | ||
| $result = [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($jsonContainingKeysWithDifferentCasing, $true, [ref]$errorRecord) | ||
| $result | Should Not Be $null |
There was a problem hiding this comment.
Please validate the returned object
There was a problem hiding this comment.
This one is resolved. Thanks.
…quested in PR. Unfortunately, Pester (3.4) does not seem to support equality assertion for hashtables...
|
@iSazonov @SteveL-MSFT I added more validation to the positive test cases and shortened the error message a bit. I opened issue #5199 for the problem with Newtonsoft.Json for keys with matching cases, which is unrelated to this PR. |
| $result = [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($jsonWithEmptyKey, $true, [ref]$errorRecord) | ||
| $result | Should Not Be $null | ||
| $result.Count | Should Be 1 | ||
| $result.$('') | Should Be 'Value' |
There was a problem hiding this comment.
You should simplify this to just: $result."" | Should Be 'Value'
|
@SteveL-MSFT I see that the documentation here has not been updated yet for the new |
|
@bergmeister Feel free to open an Issue in docs repo or push PR with the fix. |
|
@bergmeister preferably submit a PR to powershell-docs repo. The |
Address #3623.
Implementation symmetric to traditional code path that returns a
PSCustomObject. Code is shared on the top level but 2 low level methods were difficult to share, therefore 2 separate but similar methods were created for HashTables.All existing tests related to this change were adapted to also test against this new switch and were slightly improved.
Update (@TravisEz13):
-AsHashtableis an existing pattern used inGroup-Object