Add configurable maximum depth in ConvertFrom-Json with -Depth - #8199
Conversation
There was a problem hiding this comment.
I think it should be:
| public int? MaxDepth { get; set; } | |
| public int MaxDepth { get; set; } = int.MaxValue; |
There was a problem hiding this comment.
Don't think we have any default cmdlets with nullable input types, so I don't see any need for that. I would think that since it's a MaxDepth parameter we could either:
- Opt for
uint(because negative depth is insensible) with a maximum set even perhaps at something comparable to theConvertTo-Jsoncommand which has2as its default-MaxDepth, or - Simply set the default to
-1and have the code paths treat that as "no maximum depth".
There was a problem hiding this comment.
There was a problem hiding this comment.
Thank you for the feedback, as this is my first feature contribution, I wasn't sure how to implement everything 😅
Ilya (@iSazonov) your suggestion would mean there would be no way to differentiate no maximum depth and the user wanting a maximum depth of 2147483647. I think the value representing no maximum depth should really be outside of the user input range for maximum depth (1 - 2147483647)!
Rain Sallow (/u/ta11ow) (@vexx32) I could do a uint with range of 1 - int.MaxValue and use 0 as default value representing no maximum. I could also do int with -1 as default value representing no maximum. If that's what the team prefers, I don't mind, I was just using null as a way to represent the parameter not being specified. 😄
There was a problem hiding this comment.
Yeah, you have a point there; a maximum depth of 0 is also pretty silly. However, given that the underlying json API uses int for these parameters, as Ilya (@iSazonov) pointed out, we can't safely use uint as it would allow the user to specify values that the underlying API can't handle.
I think we need some clarification on whether the json API itself has a defined specification for "no maximum depth" and what that would look like.
Mark Kraus (@markekraus) I know you worked on Invoke-RestMethod a good bit, do you have any insight to offer on the json API that might help here?
There was a problem hiding this comment.
Rain Sallow (/u/ta11ow) (@vexx32) I meant I could do uint with [ValidateRange(1, int.MaxValue)], effectively not allowing user input values outside the underlying API. (but we'll have to cast the value so it might just be better to use int directly?)
Also, I was proposing 0 because the underlying API doesn't allow 0 for MaxDepth, only 1 - int.MaxValue (it throws an exception at runtime, see here).
There was a problem hiding this comment.
Oh, that's just funny at that point. If you're going to throw that early... why not just make it uint haha!
There was a problem hiding this comment.
The underlying type is an int, so we should probably accept an int with the [ValidateRange(1, int.MaxValue)]. realistically, an JSON with a depth greater than int32.MaxValue is probably bad JSON to begin with.... A nullable type for this would be weird. accepting -1 is also weird.
|
@PowerShell/powershell-committee reviewed this, the design should be that there is a |
|
Steve Lee (@SteveL-MSFT) with all due respect, may I ask to reconsider this decision? The
PS C:\> ConvertTo-Json (ConvertFrom-Json '{"1":{"2":{"3":{"4":{"5":1}}}}}')
{
"1": {
"2": {
"3": "@{4=}"
}
}
}
Furthermore, I believe the current I also think putting I know it would be a breaking change to have no maximum be the new default, but is it really a common scenario that someone is relying on the default behavior failing with input of depth higher than 1024? I think handling higher depth with this new parameter is the perfect opportunity to change the default behavior to something more intuitive: the cmdlets should handle whatever is thrown at them, if the user expects the input to cause a DoS in their system (which I assume was the reason for this default max depth), then they should specify a maximum depth optionally. |
|
So we should discuss:
My thoughts:
/cc Mark Kraus (@markekraus) Michael Klement (@mklement0) What do you think? |
|
Adam Gauthier (@louistio) to be clear, the decision regarding It would also be useful if you can provide examples where the current limit of 1024 is insufficient. |
|
Steve Lee (@SteveL-MSFT) Thank you! To be clear the naming is not a big concern to me, it's more about the bad user experience of having a default that I described later in my post. 😄 |
|
@PowerShell/powershell-committee had a very bikeshed conversation about this, and we understand the parallel concepts argument with this rough visualization: Does anyone else actually care? |
|
Two cents:
|
|
Mark Kraus (@markekraus) Michael Klement (@mklement0) Have you any thoughts about consistency of Depth parameters? |
|
I agree with Rob Holt (@rjmholt) |
|
Joey Aiello (@joeyaiello) Steve Lee (@SteveL-MSFT) Seems we can make final conclusion and continue the PR. |
|
In terms of naming, Regrettably, exceeding that maximum depth doesn't cause an error in In a utopian world, unencumbered by backward-compatibility concerns:
|
|
Rob Holt (@rjmholt) Fair point about powershell being secure by default. Michael Klement (@mklement0) I agree with everything you're saying, I think you basically put into words what I was struggling to convey. I like the idea of another switch for no I would like to implement an equivalent to |
|
Adam Gauthier (@louistio) What would be the difference between |
|
Mark Kraus (@markekraus) As mentioned above, the behavior difference resides in the cmdlet failing on input exceeding the maximum depth. Currently What I'm saying is I'd be fine with changing |
|
I think it would be confusing to have both Depth and Max Depth. We should have one for the depth and one to adjust the behavior of that depth. |
|
True, having both
(What is conceptually)
I personally then don't see a need for any other parameters - I think opting into levels beyond the fixed limit with an explicit number or even with In the absence of using
As for required changes and backward compatibility:
|
|
There are 2 things: 1) The maximum depth you want to convert and 2) the behavior of that conversion. They both need to be configurable, IMO and just relying on a single param for both would be a pain. There are a very large number of objects passed to Consider the Current behavior:
I would ultimately like to see it as this:
Ideally we would would want a But, keeping in mind that we could add parameters to control the behavior later, for this PR all we need is to add A bit of flavor text: A JSON object over 1024 in depth is an outlier. Other than abused/broken APIs or contrived examples, I have rarely seen a legit JSON objet that is deeper than ~30. On the flip-side, infinitely deep objects are very commonly thrown at ConvertTo-Json. (Based on my experience, YMMV.) |
I think it is better to make both changes for Depth in the PR to get clean history and continue with rest ideas in follow PRs. |
|
Thanks for the analysis, Mark Kraus (@markekraus), but I still think
To put it differently: the hard-coded max. depth is then no longer a default value for If you have a "runaway" tree of infinite depth, then the hard-coded max. will save you. |
|
@PowerShell/powershell-committee appreciates the discussion and we still suggest that we have the single |
-Depth
|
Mark Kraus (@markekraus) Michael Klement (@mklement0) Steve Lee (@SteveL-MSFT) Please update your review. Adam Gauthier (@louistio) Please fix StyleCop issues. |
| MetadataPropertyHandling = MetadataPropertyHandling.Ignore | ||
| }; | ||
|
|
||
| if (maxDepth != null) |
There was a problem hiding this comment.
.MaxDepth == null means no limit, so it doesn't seem this check is necessary
| @{ Depth = 2; AsHashtable = $false } | ||
| @{ Depth = 200; AsHashtable = $true } | ||
| @{ Depth = 200; AsHashtable = $false } | ||
| @{ Depth = 2000; AsHashtable = $true } |
There was a problem hiding this comment.
How long does this take to run? If longer than 2 secs, we should have them as Feature tests instead of CI.
|
|
||
| It 'Fails to convert an object of depth higher than 1024 by default with AsHashtable switch set to <AsHashtable>' -TestCases $testCasesWithAndWithoutAsHashtableSwitch { | ||
| Param($AsHashtable) | ||
| $nestedJson = GenerateNestedJson -Depth:1989 |
There was a problem hiding this comment.
wouldn't 1025 be sufficient? no need to spend extra cpu here
| @{ Depth = 2000; AsHashtable = $false } | ||
| ) | ||
|
|
||
| function GenerateNestedJson { |
There was a problem hiding this comment.
prefer New-NestedJson
| function Count-ObjectDepth { | ||
| Param([PSCustomObject] $InputObject) | ||
|
|
||
| for ($i=1; $i -le 100000; $i++) |
There was a problem hiding this comment.
Since you have a max depth for checking here, you should probably enforce this limit on new-nestedjson. Also, I'm not sure we need to test this deep, perhaps 2048 is sufficient
Ilya (iSazonov)
left a comment
There was a problem hiding this comment.
Adam Gauthier (@louistio) Have you time to continue?
Co-Authored-By: louistio <[email protected]>
|
Ilya (@iSazonov) Hey! Sorry, I wasn't aware of the colon convention 😅 I applied your suggestions through GitHub's integrated tool, but there seems to be error when running the tests without the colons! I get errors like: I'm not super familiar as to why removing colons would cause this, could you point me into the right direction? Thanks! |
|
Adam Gauthier (@louistio) Sorry, colons is needed for asHashtable but not for Depth. |
|
Steve Lee (@SteveL-MSFT) Mark Kraus (@markekraus) Please update your review. |
Mark Kraus (markekraus)
left a comment
There was a problem hiding this comment.
Minor cleanup requested.
Co-Authored-By: louistio <[email protected]>
|
Steve Lee (@SteveL-MSFT) Please update your review. |
|
Adam Gauthier (@louistio) Please update the PR description and we'll merge. |
|
Ilya (@iSazonov) description updated, I believe this still requires documentation changes, not sure if that prevents from merging. |
|
Adam Gauthier (@louistio) Please open new Issue in PowerShell-Docs repo and add a reference to the PR description. |
|
Ilya (@iSazonov) done! |
|
Adam Gauthier (@louistio) Thanks for great work! |
…erShell#8199) Adds an optional -Depth parameter to the cmdlet which lets the user to specify a maximum depth allowed for deserialization, which will overwrite the default maximum of 1024.
PR Summary
The
ConvertFrom-Jsoncmdlet is currently limited to deserializing json objects with a maximum depth of 1024. An exception is thrown if the input's depth reaches that maximum.This PR:
-Depthparameter to the cmdlet which lets the user to specify a maximum depth allowed for deserialization, which will overwrite the default maximum of1024.Closes #3182
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:to the beginning of the title and remove the prefix when the PR is ready.[feature]if the change is significant or affects feature tests