Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Fixed deserialization of null arrays for issue #37606#37616

Merged
ahsonkhan merged 2 commits intodotnet:masterfrom
gnovack:NullRootArrays
May 16, 2019
Merged

Fixed deserialization of null arrays for issue #37606#37616
ahsonkhan merged 2 commits intodotnet:masterfrom
gnovack:NullRootArrays

Conversation

@gnovack
Copy link
Copy Markdown
Contributor

@gnovack gnovack commented May 12, 2019

This fixes #37606 by checking for null before adding to state.Current.ReturnValue
Also added tests for deserializing null arrays.

@dnfclas
Copy link
Copy Markdown

dnfclas commented May 12, 2019

CLA assistant check
All CLA requirements met.

@danmoseley danmoseley requested a review from steveharter May 13, 2019 01:36
{
int[] obj = JsonSerializer.Parse<int[]>("null");
Assert.Null(obj);
}
Copy link
Copy Markdown
Member

@MarcoRossignoli MarcoRossignoli May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not used to see scope style on codebase but I could wrong @danmosemsft

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Contributor

@steveharter steveharter May 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>).

@ahsonkhan ahsonkhan added this to the 3.0 milestone May 14, 2019
}

[Fact]
public static void RootArrayIsNull()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

public static void ReadObjectFail()

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):

public static void DefaultIgnoreNullValuesOnRead()

Copy link
Copy Markdown

@ahsonkhan ahsonkhan May 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

public static IEnumerable<object[]> InvalidJsonStrings

// 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))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a helper called IsLastFrame on the stack frame that could be used instead of CurrentDepth but otherwise looks correct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than leftover comment to add a test for "array within a class" (i.e. the following JSON: "{null}"),
LGTM. Thanks for contributing.

Edit: ignore test comment

@ahsonkhan ahsonkhan merged commit 026fa6a into dotnet:master May 16, 2019
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…otnet/corefx#37616)

* Fixed deserialization of null root arrays.

* Used reader.CurrentDepth to catch root level nulls.


Commit migrated from dotnet/corefx@026fa6a
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[System.Text.Json] Deserializing null array throws null-ref

7 participants