Fixed deserialization of null arrays for issue #37606#37616
Fixed deserialization of null arrays for issue #37606#37616ahsonkhan merged 2 commits intodotnet:masterfrom
Conversation
| { | ||
| int[] obj = JsonSerializer.Parse<int[]>("null"); | ||
| Assert.Null(obj); | ||
| } |
There was a problem hiding this comment.
I'm not used to see scope style on codebase but I could wrong @danmosemsft
There was a problem hiding this comment.
I'm not either, but I don't think we're against it either - particularly in test code if it makes things clearer.
In this case though arguably it's a "code smell" suggesting there should be 3 separate test methods.
There was a problem hiding this comment.
I'm not a huge fan of it myself, but it looked like an existing pattern in the test classes for short, repetitive tests like these.
There was a problem hiding this comment.
Yes it's only like this in tests and is there to avoid multiple tests and prevent these sections from accidently colliding on variable names. Sometimes it's used to verify a baseline condition before testing the primary case.
| state.Current.TempEnumerableValues.Add(value); | ||
| } | ||
| else | ||
| else if (state.Current.ReturnValue != null) |
There was a problem hiding this comment.
It seems like this is the wrong code path since the value should never be added to a list.
Instead this code path should be the one returning null:
however it isn't getting into that condition since the JsonPropertyInfo is not null. It is possible to expand this line\condition to cover this issue it may be better to refactor this code to support this condition without special casing (for example by not having a null JsonPropertyInfo ever) - not sure until I see both ways.
There was a problem hiding this comment.
I see what you mean. It probably should never reach ApplyObjectToEnumerable.
I'll try to see if there's a clean way to catch root-level nulls within HandleNull.
Side note: Looks like this issue encompasses everything of ClassType.Enumerable.
The following test throws null-ref as well:
IEnumerable<object> obj = JsonSerializer.Parse<IEnumerable<object>>("null");
There was a problem hiding this comment.
Side note: Looks like this issue encompasses everything of
ClassType.Enumerable.
@gnovack, while you are investigating this space, can you please add tests for similar cases like the one you mentioned? Similarly, Parse<T> where T is different types: value types (int, DateTime, etc.) and reference types (string, some POCO class, Dictionary<TKey, TValue>, and as you mentioned IEnumerable<T>).
| } | ||
|
|
||
| [Fact] | ||
| public static void RootArrayIsNull() |
There was a problem hiding this comment.
It might be good to add a test for an array within a class as well.
public class Foo
{
public int[] Array {get; set;}
}
Foo obj = JsonSerializer.Parse<Foo>("{null}");There was a problem hiding this comment.
The above test throws a JsonException because "{null}" isn't a valid JSON string, but I'd be happy to add this test if you'd like. It would probably belong here:
As far as testing a valid JSON object that contains a null array, it looks like that's covered by the following, which deserializes an object with various types of properties set to null (including an array):
There was a problem hiding this comment.
Ah yes, you are right. Good point! I meant testing with a json payload like {"Array": null}, which you rightfully pointed out, is covered.
I'd be happy to add this test if you'd like
That isn't necessary, at least it isn't worth resetting CI over :)
I am sure the underlying reader will catch this, though it would be nice to add it to:
| // If we don't have a valid property, that means we read "null" for a root object so just return. | ||
| if (state.Current.JsonPropertyInfo == null) | ||
| // If null is read at the top level and the type is nullable, then the root is "null" so just return. | ||
| if (reader.CurrentDepth == 0 && (state.Current.JsonPropertyInfo == null || state.Current.JsonPropertyInfo.CanBeNull)) |
There was a problem hiding this comment.
Since this code flow ultimately results in a return from ReadCore, it should only ever be triggered at the top level (reader.CurrentDepth == 0)
Second condition is to ensure that the type we're dealing with is nullable.
There was a problem hiding this comment.
There is a helper called IsLastFrame on the stack frame that could be used instead of CurrentDepth but otherwise looks correct.
There was a problem hiding this comment.
IsLastFrame behaves slightly different from reader.CurrentDepth.
For example, when the reader encounters null in the following json { "MyProperty": null }, reader.CurrentDepth is 1, but IsLastFrame is true.
IsLastFrame appears to only update when a new frame is pushed onto the stack, which happens while parsing sub-objects/sub-arrays within an object.
…otnet/corefx#37616) * Fixed deserialization of null root arrays. * Used reader.CurrentDepth to catch root level nulls. Commit migrated from dotnet/corefx@026fa6a
This fixes #37606 by checking for null before adding to
state.Current.ReturnValueAlso added tests for deserializing null arrays.