Skip to content

Commit 28f729b

Browse files
authored
Make GetPropertyCount public and fix its return value (#106503)
* 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`
1 parent bb1b43a commit 28f729b

File tree

6 files changed

+77
-14
lines changed

6 files changed

+77
-14
lines changed

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public readonly partial struct JsonElement
6666
public System.Text.Json.JsonElement GetProperty(System.ReadOnlySpan<byte> utf8PropertyName) { throw null; }
6767
public System.Text.Json.JsonElement GetProperty(System.ReadOnlySpan<char> propertyName) { throw null; }
6868
public System.Text.Json.JsonElement GetProperty(string propertyName) { throw null; }
69+
public int GetPropertyCount() { throw null; }
6970
public string GetRawText() { throw null; }
7071
[System.CLSCompliantAttribute(false)]
7172
public sbyte GetSByte() { throw null; }

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.MetadataDb.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public sealed partial class JsonDocument
6868
// * 31 bits for token offset
6969
// * Second int
7070
// * Top bit is unassigned / always clear
71-
// * 31 bits for the token length (always 1, effectively unassigned)
71+
// * 31 bits for the number of properties in this object
7272
// * Third int
7373
// * 4 bits JsonTokenType
7474
// * 28 bits for the number of rows until the next value (never 0)

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,7 @@ private static void Parse(
950950
ref StackRowStack stack)
951951
{
952952
bool inArray = false;
953-
int arrayItemsCount = 0;
953+
int arrayItemsOrPropertyCount = 0;
954954
int numberOfRowsForMembers = 0;
955955
int numberOfRowsForValues = 0;
956956

@@ -972,13 +972,14 @@ private static void Parse(
972972
{
973973
if (inArray)
974974
{
975-
arrayItemsCount++;
975+
arrayItemsOrPropertyCount++;
976976
}
977977

978978
numberOfRowsForValues++;
979979
database.Append(tokenType, tokenStart, DbRow.UnknownSize);
980-
var row = new StackRow(numberOfRowsForMembers + 1);
980+
var row = new StackRow(arrayItemsOrPropertyCount, numberOfRowsForMembers + 1);
981981
stack.Push(row);
982+
arrayItemsOrPropertyCount = 0;
982983
numberOfRowsForMembers = 0;
983984
}
984985
else if (tokenType == JsonTokenType.EndObject)
@@ -987,28 +988,29 @@ private static void Parse(
987988

988989
numberOfRowsForValues++;
989990
numberOfRowsForMembers++;
990-
database.SetLength(rowIndex, numberOfRowsForMembers);
991+
database.SetLength(rowIndex, arrayItemsOrPropertyCount);
991992

992993
int newRowIndex = database.Length;
993994
database.Append(tokenType, tokenStart, reader.ValueSpan.Length);
994995
database.SetNumberOfRows(rowIndex, numberOfRowsForMembers);
995996
database.SetNumberOfRows(newRowIndex, numberOfRowsForMembers);
996997

997998
StackRow row = stack.Pop();
998-
numberOfRowsForMembers += row.SizeOrLength;
999+
arrayItemsOrPropertyCount = row.SizeOrLength;
1000+
numberOfRowsForMembers += row.NumberOfRows;
9991001
}
10001002
else if (tokenType == JsonTokenType.StartArray)
10011003
{
10021004
if (inArray)
10031005
{
1004-
arrayItemsCount++;
1006+
arrayItemsOrPropertyCount++;
10051007
}
10061008

10071009
numberOfRowsForMembers++;
10081010
database.Append(tokenType, tokenStart, DbRow.UnknownSize);
1009-
var row = new StackRow(arrayItemsCount, numberOfRowsForValues + 1);
1011+
var row = new StackRow(arrayItemsOrPropertyCount, numberOfRowsForValues + 1);
10101012
stack.Push(row);
1011-
arrayItemsCount = 0;
1013+
arrayItemsOrPropertyCount = 0;
10121014
numberOfRowsForValues = 0;
10131015
}
10141016
else if (tokenType == JsonTokenType.EndArray)
@@ -1017,7 +1019,7 @@ private static void Parse(
10171019

10181020
numberOfRowsForValues++;
10191021
numberOfRowsForMembers++;
1020-
database.SetLength(rowIndex, arrayItemsCount);
1022+
database.SetLength(rowIndex, arrayItemsOrPropertyCount);
10211023
database.SetNumberOfRows(rowIndex, numberOfRowsForValues);
10221024

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

10421044
StackRow row = stack.Pop();
1043-
arrayItemsCount = row.SizeOrLength;
1045+
arrayItemsOrPropertyCount = row.SizeOrLength;
10441046
numberOfRowsForValues += row.NumberOfRows;
10451047
}
10461048
else if (tokenType == JsonTokenType.PropertyName)
10471049
{
10481050
numberOfRowsForValues++;
10491051
numberOfRowsForMembers++;
1052+
arrayItemsOrPropertyCount++;
10501053

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

10691072
if (inArray)
10701073
{
1071-
arrayItemsCount++;
1074+
arrayItemsOrPropertyCount++;
10721075
}
10731076

10741077
if (tokenType == JsonTokenType.String)

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public int GetArrayLength()
9696
/// <exception cref="ObjectDisposedException">
9797
/// The parent <see cref="JsonDocument"/> has been disposed.
9898
/// </exception>
99-
internal int GetPropertyCount()
99+
public int GetPropertyCount()
100100
{
101101
CheckValidInstance();
102102

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonDocumentTests.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ public static void ParseSimpleObject()
681681
string city = parsedObject.GetProperty("city").GetString();
682682
int zip = parsedObject.GetProperty("zip").GetInt32();
683683

684+
Assert.Equal(7, parsedObject.GetPropertyCount());
684685
Assert.True(parsedObject.TryGetProperty("age", out JsonElement age2));
685686
Assert.Equal(30, age2.GetInt32());
686687

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

705706
Assert.Equal(1, parsedObject.GetArrayLength());
706707
JsonElement person = parsedObject[0];
708+
Assert.Equal(5, person.GetPropertyCount());
707709
double age = person.GetProperty("age").GetDouble();
708710
string first = person.GetProperty("first").GetString();
709711
string last = person.GetProperty("last").GetString();
@@ -902,6 +904,7 @@ public static void ReadNumber_1Byte(sbyte value)
902904
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
903905
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
904906
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
907+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
905908
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
906909
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
907910
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -999,6 +1002,7 @@ public static void ReadNumber_2Bytes(short value)
9991002
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
10001003
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
10011004
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1005+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
10021006
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
10031007
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
10041008
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1091,6 +1095,7 @@ public static void ReadSmallInteger(int value)
10911095
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
10921096
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
10931097
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1098+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
10941099
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
10951100
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
10961101
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1194,6 +1199,7 @@ public static void ReadMediumInteger(long value)
11941199
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
11951200
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
11961201
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1202+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
11971203
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
11981204
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
11991205
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1267,6 +1273,7 @@ public static void ReadLargeInteger(ulong value)
12671273
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
12681274
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
12691275
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1276+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
12701277
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
12711278
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
12721279
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1340,6 +1347,7 @@ public static void ReadTooLargeInteger()
13401347
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
13411348
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
13421349
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1350+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
13431351
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
13441352
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
13451353
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1379,6 +1387,7 @@ public static void ReadDateTimeAndDateTimeOffset(string jsonString, string expec
13791387
Assert.Throws<InvalidOperationException>(() => root.GetInt64());
13801388
Assert.Throws<InvalidOperationException>(() => root.GetUInt64());
13811389
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1390+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
13821391
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
13831392
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
13841393
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1462,6 +1471,7 @@ public static void ReadGuid(string jsonString, string expectedStr)
14621471
Assert.Throws<InvalidOperationException>(() => root.GetInt64());
14631472
Assert.Throws<InvalidOperationException>(() => root.GetUInt64());
14641473
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1474+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
14651475
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
14661476
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
14671477
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1564,6 +1574,7 @@ public static void ReadNonInteger(string str, double expectedDouble, float expec
15641574
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
15651575
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
15661576
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1577+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
15671578
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
15681579
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
15691580
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1654,6 +1665,7 @@ public static void ReadTooPreciseDouble()
16541665
Assert.Throws<InvalidOperationException>(() => root.GetDateTimeOffset());
16551666
Assert.Throws<InvalidOperationException>(() => root.GetGuid());
16561667
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1668+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
16571669
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
16581670
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
16591671
Assert.Throws<InvalidOperationException>(() => root.GetBoolean());
@@ -1735,6 +1747,7 @@ public static void CheckUseAfterDispose()
17351747

17361748
Assert.Throws<ObjectDisposedException>(() => root.ValueKind);
17371749
Assert.Throws<ObjectDisposedException>(() => root.GetArrayLength());
1750+
Assert.Throws<ObjectDisposedException>(() => root.GetPropertyCount());
17381751
Assert.Throws<ObjectDisposedException>(() => root.EnumerateArray());
17391752
Assert.Throws<ObjectDisposedException>(() => root.EnumerateObject());
17401753
Assert.Throws<ObjectDisposedException>(() => root.GetDouble());
@@ -1793,6 +1806,7 @@ public static void CheckUseDefault()
17931806
Assert.Equal(JsonValueKind.Undefined, root.ValueKind);
17941807

17951808
Assert.Throws<InvalidOperationException>(() => root.GetArrayLength());
1809+
Assert.Throws<InvalidOperationException>(() => root.GetPropertyCount());
17961810
Assert.Throws<InvalidOperationException>(() => root.EnumerateArray());
17971811
Assert.Throws<InvalidOperationException>(() => root.EnumerateObject());
17981812
Assert.Throws<InvalidOperationException>(() => root.GetDouble());
@@ -2442,13 +2456,15 @@ public static void GetRawText()
24422456

24432457
using (JsonDocument doc = JsonDocument.Parse(json))
24442458
{
2459+
Assert.Equal(6, doc.RootElement.GetPropertyCount());
24452460
JsonElement.ObjectEnumerator enumerator = doc.RootElement.EnumerateObject();
24462461
Assert.True(enumerator.MoveNext(), "Move to first property");
24472462
JsonProperty property = enumerator.Current;
24482463

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

3456+
[Theory]
3457+
[InlineData("""{ "foo" : [1], "test": false, "bar" : { "nested": 3 } }""", 3)]
3458+
[InlineData("""{ "foo" : [1,2,3,4] }""", 1)]
3459+
[InlineData("""{}""", 0)]
3460+
[InlineData("""{ "foo" : {"nested:" : {"nested": 1, "bla": [1, 2, {"bla": 3}] } }, "test": true, "foo2" : {"nested:" : {"nested": 1, "bla": [1, 2, {"bla": 3}] } }}""", 3)]
3461+
public static void TestGetPropertyCount(string json, int expectedCount)
3462+
{
3463+
JsonElement element = JsonSerializer.Deserialize<JsonElement>(json);
3464+
Assert.Equal(expectedCount, element.GetPropertyCount());
3465+
}
3466+
3467+
[Fact]
3468+
public static void VerifyGetPropertyCountAndArrayLengthUsingEnumerateMethods()
3469+
{
3470+
using (JsonDocument doc = JsonDocument.Parse(SR.ProjectLockJson))
3471+
{
3472+
CheckPropertyCountAndArrayLengthAgainstEnumerateMethods(doc.RootElement);
3473+
}
3474+
3475+
void CheckPropertyCountAndArrayLengthAgainstEnumerateMethods(JsonElement elem)
3476+
{
3477+
if (elem.ValueKind == JsonValueKind.Object)
3478+
{
3479+
Assert.Equal(elem.EnumerateObject().Count(), elem.GetPropertyCount());
3480+
foreach (JsonProperty prop in elem.EnumerateObject())
3481+
{
3482+
CheckPropertyCountAndArrayLengthAgainstEnumerateMethods(prop.Value);
3483+
}
3484+
}
3485+
else if (elem.ValueKind == JsonValueKind.Array)
3486+
{
3487+
Assert.Equal(elem.EnumerateArray().Count(), elem.GetArrayLength());
3488+
foreach (JsonElement item in elem.EnumerateArray())
3489+
{
3490+
CheckPropertyCountAndArrayLengthAgainstEnumerateMethods(item);
3491+
}
3492+
}
3493+
}
3494+
}
3495+
34403496
[Fact]
34413497
public static void EnsureResizeSucceeds()
34423498
{

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Stream.ReadTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,14 @@ public async Task TestBOMWithShortAndLongBuffers(Stream stream, int count, int e
220220
void VerifyElement(int index)
221221
{
222222
Assert.Equal(JsonValueKind.Object, value[index].GetProperty("Test").ValueKind);
223+
Assert.Equal(0, value[index].GetProperty("Test").GetPropertyCount());
223224
Assert.False(value[index].GetProperty("Test").EnumerateObject().MoveNext());
224225
Assert.Equal(JsonValueKind.Array, value[index].GetProperty("Test2").ValueKind);
225226
Assert.Equal(0, value[index].GetProperty("Test2").GetArrayLength());
226227
Assert.Equal(JsonValueKind.Object, value[index].GetProperty("Test3").ValueKind);
227228
Assert.Equal(JsonValueKind.Object, value[index].GetProperty("Test3").GetProperty("Value").ValueKind);
229+
Assert.Equal(1, value[index].GetProperty("Test3").GetPropertyCount());
230+
Assert.Equal(0, value[index].GetProperty("Test3").GetProperty("Value").GetPropertyCount());
228231
Assert.False(value[index].GetProperty("Test3").GetProperty("Value").EnumerateObject().MoveNext());
229232
Assert.Equal(0, value[index].GetProperty("PersonType").GetInt32());
230233
Assert.Equal(2, value[index].GetProperty("Id").GetInt32());

0 commit comments

Comments
 (0)