-
Notifications
You must be signed in to change notification settings - Fork 7.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add -AsHashtable switch to ConvertFrom-Json for issue #3623 #5043
Add -AsHashtable switch to ConvertFrom-Json for issue #3623 #5043
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bergmeister Thanks for great contribution!
[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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't change a public API. Please add new overload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 = "object"; str = "{a:1}"; ReturnHashTable = $true } | ||
@{ name = "empty"; str = ""; ReturnHashTable = $false } | ||
@{ name = "spaces"; str = " "; ReturnHashTable = $false } | ||
@{ name = "object"; str = "{a:1}"; ReturnHashTable = $false } | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move to BeforeAll block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe 'no error for valid string - with -ReturnHashTable:`$<ReturnHashTable>'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is default. Please remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by that? Can you elaborate please (what is 'it')?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Parameter(Mandatory = false)]
is default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I see, thanks.
else | ||
{ | ||
obj = PopulateFromJArray(list, out error); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add 'else' with Diagnostics.Assert (if the object is not JObject or JArray).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -171,7 +171,7 @@ private bool TryConvertToJson(string json, out object obj, ref Exception exRef) | |||
try | |||
{ | |||
ErrorRecord error; | |||
obj = JsonObject.ConvertFromJson(json, out error); | |||
obj = JsonObject.ConvertFromJson(json, false, out error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert the change - we have the overload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we have a check for input == null
but haven't the test - please add new test with str = $null
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Added
…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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably capitalize json as JSON
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Fixed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we address the comment about case sensitivity?
) | ||
|
||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same.
{ [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson($str, $ReturnHashTable, [ref]$errRecord) } | ShouldBeErrorId "ArgumentException" | ||
} | ||
|
||
$jsonWithEmptyKey = '{"": "Value"}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please start test descriptions with capital.
$jsonWithEmptyKey = '{"": "Value"}' | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, very long message!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to check the hashtable values.
$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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please validate the returned object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please validate the returned object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please validate the returned object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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):
-AsHashtable
is an existing pattern used inGroup-Object