Skip to content

Commit

Permalink
[release/9.0] Make GetPropertyCount public and fix its return value (#…
Browse files Browse the repository at this point in the history
…106576)

* Make GetPropertyCount public and fix its return value

* Change order of assignments to match array counterpart

* Reuse local variable

* Show that GetPropertyCount() and EnumerateObject().Count() are the same at every object node

* Fix test to also check objects inside arrays

Also added checks for `GetArrayLength`

---------

Co-authored-by: etemi <40637181+etemi@users.noreply.github.com>
  • Loading branch information
github-actions[bot] and etemi committed Aug 27, 2024
1 parent a92301b commit 980daff
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public readonly partial struct JsonElement
public System.Text.Json.JsonElement GetProperty(System.ReadOnlySpan<byte> utf8PropertyName) { throw null; }
public System.Text.Json.JsonElement GetProperty(System.ReadOnlySpan<char> propertyName) { throw null; }
public System.Text.Json.JsonElement GetProperty(string propertyName) { throw null; }
public int GetPropertyCount() { throw null; }
public string GetRawText() { throw null; }
[System.CLSCompliantAttribute(false)]
public sbyte GetSByte() { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public sealed partial class JsonDocument
// * 31 bits for token offset
// * Second int
// * Top bit is unassigned / always clear
// * 31 bits for the token length (always 1, effectively unassigned)
// * 31 bits for the number of properties in this object
// * Third int
// * 4 bits JsonTokenType
// * 28 bits for the number of rows until the next value (never 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ private static void Parse(
ref StackRowStack stack)
{
bool inArray = false;
int arrayItemsCount = 0;
int arrayItemsOrPropertyCount = 0;
int numberOfRowsForMembers = 0;
int numberOfRowsForValues = 0;

Expand All @@ -972,13 +972,14 @@ private static void Parse(
{
if (inArray)
{
arrayItemsCount++;
arrayItemsOrPropertyCount++;
}

numberOfRowsForValues++;
database.Append(tokenType, tokenStart, DbRow.UnknownSize);
var row = new StackRow(numberOfRowsForMembers + 1);
var row = new StackRow(arrayItemsOrPropertyCount, numberOfRowsForMembers + 1);
stack.Push(row);
arrayItemsOrPropertyCount = 0;
numberOfRowsForMembers = 0;
}
else if (tokenType == JsonTokenType.EndObject)
Expand All @@ -987,28 +988,29 @@ private static void Parse(

numberOfRowsForValues++;
numberOfRowsForMembers++;
database.SetLength(rowIndex, numberOfRowsForMembers);
database.SetLength(rowIndex, arrayItemsOrPropertyCount);

int newRowIndex = database.Length;
database.Append(tokenType, tokenStart, reader.ValueSpan.Length);
database.SetNumberOfRows(rowIndex, numberOfRowsForMembers);
database.SetNumberOfRows(newRowIndex, numberOfRowsForMembers);

StackRow row = stack.Pop();
numberOfRowsForMembers += row.SizeOrLength;
arrayItemsOrPropertyCount = row.SizeOrLength;
numberOfRowsForMembers += row.NumberOfRows;
}
else if (tokenType == JsonTokenType.StartArray)
{
if (inArray)
{
arrayItemsCount++;
arrayItemsOrPropertyCount++;
}

numberOfRowsForMembers++;
database.Append(tokenType, tokenStart, DbRow.UnknownSize);
var row = new StackRow(arrayItemsCount, numberOfRowsForValues + 1);
var row = new StackRow(arrayItemsOrPropertyCount, numberOfRowsForValues + 1);
stack.Push(row);
arrayItemsCount = 0;
arrayItemsOrPropertyCount = 0;
numberOfRowsForValues = 0;
}
else if (tokenType == JsonTokenType.EndArray)
Expand All @@ -1017,7 +1019,7 @@ private static void Parse(

numberOfRowsForValues++;
numberOfRowsForMembers++;
database.SetLength(rowIndex, arrayItemsCount);
database.SetLength(rowIndex, arrayItemsOrPropertyCount);
database.SetNumberOfRows(rowIndex, numberOfRowsForValues);

// If the array item count is (e.g.) 12 and the number of rows is (e.g.) 13
Expand All @@ -1030,7 +1032,7 @@ private static void Parse(
// This check is similar to tracking the start array and painting it when
// StartObject or StartArray is encountered, but avoids the mixed state
// where "UnknownSize" implies "has complex children".
if (arrayItemsCount + 1 != numberOfRowsForValues)
if (arrayItemsOrPropertyCount + 1 != numberOfRowsForValues)
{
database.SetHasComplexChildren(rowIndex);
}
Expand All @@ -1040,13 +1042,14 @@ private static void Parse(
database.SetNumberOfRows(newRowIndex, numberOfRowsForValues);

StackRow row = stack.Pop();
arrayItemsCount = row.SizeOrLength;
arrayItemsOrPropertyCount = row.SizeOrLength;
numberOfRowsForValues += row.NumberOfRows;
}
else if (tokenType == JsonTokenType.PropertyName)
{
numberOfRowsForValues++;
numberOfRowsForMembers++;
arrayItemsOrPropertyCount++;

// Adding 1 to skip the start quote will never overflow
Debug.Assert(tokenStart < int.MaxValue);
Expand All @@ -1068,7 +1071,7 @@ private static void Parse(

if (inArray)
{
arrayItemsCount++;
arrayItemsOrPropertyCount++;
}

if (tokenType == JsonTokenType.String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public int GetArrayLength()
/// <exception cref="ObjectDisposedException">
/// The parent <see cref="JsonDocument"/> has been disposed.
/// </exception>
internal int GetPropertyCount()
public int GetPropertyCount()
{
CheckValidInstance();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ public static void ParseSimpleObject()
string city = parsedObject.GetProperty("city").GetString();
int zip = parsedObject.GetProperty("zip").GetInt32();

Assert.Equal(7, parsedObject.GetPropertyCount());
Assert.True(parsedObject.TryGetProperty("age", out JsonElement age2));
Assert.Equal(30, age2.GetInt32());

Expand All @@ -704,6 +705,7 @@ public static void ParseNestedJson()

Assert.Equal(1, parsedObject.GetArrayLength());
JsonElement person = parsedObject[0];
Assert.Equal(5, person.GetPropertyCount());
double age = person.GetProperty("age").GetDouble();
string first = person.GetProperty("first").GetString();
string last = person.GetProperty("last").GetString();
Expand Down Expand Up @@ -902,6 +904,7 @@ public static void ReadNumber_1Byte(sbyte value)
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -999,6 +1002,7 @@ public static void ReadNumber_2Bytes(short value)
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1091,6 +1095,7 @@ public static void ReadSmallInteger(int value)
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1194,6 +1199,7 @@ public static void ReadMediumInteger(long value)
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1267,6 +1273,7 @@ public static void ReadLargeInteger(ulong value)
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1340,6 +1347,7 @@ public static void ReadTooLargeInteger()
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1379,6 +1387,7 @@ public static void ReadDateTimeAndDateTimeOffset(string jsonString, string expec
Assert.Throws<InvalidOperationException>(() => root.GetInt64());
Assert.Throws<InvalidOperationException>(() => root.GetUInt64());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1462,6 +1471,7 @@ public static void ReadGuid(string jsonString, string expectedStr)
Assert.Throws<InvalidOperationException>(() => root.GetInt64());
Assert.Throws<InvalidOperationException>(() => root.GetUInt64());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1564,6 +1574,7 @@ public static void ReadNonInteger(string str, double expectedDouble, float expec
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1654,6 +1665,7 @@ public static void ReadTooPreciseDouble()
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
Expand Down Expand Up @@ -1735,6 +1747,7 @@ public static void CheckUseAfterDispose()

Assert.Throws<ObjectDisposedException>(() => root.ValueKind);
Assert.Throws<ObjectDisposedException>(() => root.GetArrayLength());
Assert.Throws<ObjectDisposedException>(() => root.GetPropertyCount());
Assert.Throws<ObjectDisposedException>(() => root.EnumerateArray());
Assert.Throws<ObjectDisposedException>(() => root.EnumerateObject());
Assert.Throws<ObjectDisposedException>(() => root.GetDouble());
Expand Down Expand Up @@ -1793,6 +1806,7 @@ public static void CheckUseDefault()
Assert.Equal(JsonValueKind.Undefined, root.ValueKind);

Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
Assert.Throws<InvalidOperationException>(() => root.GetDouble());
Expand Down Expand Up @@ -2442,13 +2456,15 @@ public static void GetRawText()

using (JsonDocument doc = JsonDocument.Parse(json))
{
Assert.Equal(6, doc.RootElement.GetPropertyCount());
JsonElement.ObjectEnumerator enumerator = doc.RootElement.EnumerateObject();
Assert.True(enumerator.MoveNext(), "Move to first property");
JsonProperty property = enumerator.Current;

Assert.Equal(" weird property name", property.Name);
string rawText = property.ToString();
int crCount = rawText.Count(c => c == '\r');
Assert.Equal(2, property.Value.GetPropertyCount());
Assert.Equal(128 + crCount, rawText.Length);
Assert.Equal('\"', rawText[0]);
Assert.Equal(' ', rawText[1]);
Expand Down Expand Up @@ -3437,6 +3453,46 @@ public static void ParseValue_AllowMultipleValues_TrailingContent()
JsonTestHelper.AssertThrows<JsonException>(ref reader, (ref Utf8JsonReader reader) => reader.Read());
}

[Theory]
[InlineData("""{ "foo" : [1], "test": false, "bar" : { "nested": 3 } }""", 3)]
[InlineData("""{ "foo" : [1,2,3,4] }""", 1)]
[InlineData("""{}""", 0)]
[InlineData("""{ "foo" : {"nested:" : {"nested": 1, "bla": [1, 2, {"bla": 3}] } }, "test": true, "foo2" : {"nested:" : {"nested": 1, "bla": [1, 2, {"bla": 3}] } }}""", 3)]
public static void TestGetPropertyCount(string json, int expectedCount)
{
JsonElement element = JsonSerializer.Deserialize<JsonElement>(json);
Assert.Equal(expectedCount, element.GetPropertyCount());
}

[Fact]
public static void VerifyGetPropertyCountAndArrayLengthUsingEnumerateMethods()
{
using (JsonDocument doc = JsonDocument.Parse(SR.ProjectLockJson))
{
CheckPropertyCountAndArrayLengthAgainstEnumerateMethods(doc.RootElement);
}

void CheckPropertyCountAndArrayLengthAgainstEnumerateMethods(JsonElement elem)
{
if (elem.ValueKind == JsonValueKind.Object)
{
Assert.Equal(elem.EnumerateObject().Count(), elem.GetPropertyCount());
foreach (JsonProperty prop in elem.EnumerateObject())
{
CheckPropertyCountAndArrayLengthAgainstEnumerateMethods(prop.Value);
}
}
else if (elem.ValueKind == JsonValueKind.Array)
{
Assert.Equal(elem.EnumerateArray().Count(), elem.GetArrayLength());
foreach (JsonElement item in elem.EnumerateArray())
{
CheckPropertyCountAndArrayLengthAgainstEnumerateMethods(item);
}
}
}
}

[Fact]
public static void EnsureResizeSucceeds()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,14 @@ public async Task TestBOMWithShortAndLongBuffers(Stream stream, int count, int e
void VerifyElement(int index)
{
Assert.Equal(JsonValueKind.Object, value[index].GetProperty("Test").ValueKind);
Assert.Equal(0, value[index].GetProperty("Test").GetPropertyCount());
Assert.False(value[index].GetProperty("Test").EnumerateObject().MoveNext());
Assert.Equal(JsonValueKind.Array, value[index].GetProperty("Test2").ValueKind);
Assert.Equal(0, value[index].GetProperty("Test2").GetArrayLength());
Assert.Equal(JsonValueKind.Object, value[index].GetProperty("Test3").ValueKind);
Assert.Equal(JsonValueKind.Object, value[index].GetProperty("Test3").GetProperty("Value").ValueKind);
Assert.Equal(1, value[index].GetProperty("Test3").GetPropertyCount());
Assert.Equal(0, value[index].GetProperty("Test3").GetProperty("Value").GetPropertyCount());
Assert.False(value[index].GetProperty("Test3").GetProperty("Value").EnumerateObject().MoveNext());
Assert.Equal(0, value[index].GetProperty("PersonType").GetInt32());
Assert.Equal(2, value[index].GetProperty("Id").GetInt32());
Expand Down

0 comments on commit 980daff

Please sign in to comment.