Skip to content

Commit 386669e

Browse files
authored
Add STJ.Nodes.JsonArray.Remove(All|Range) (#109472)
* Close #107962. Add RemoveAll & RemoveRange to JsonArray. * added exception messages; added tests; fixed bug on trailing elements in RemoveAll * Implement RemoveAll thru List<T>.RemoveAll with lambdas. Remove lazy-load checks in RemoveRange. * Removed unused using statements * added tests for freshly deserialized JsonArray * fix formatting * revert comments in auto-generated resource file
1 parent 9c1f53e commit 386669e

File tree

5 files changed

+210
-0
lines changed

5 files changed

+210
-0
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,8 @@ public void Clear() { }
705705
public void Insert(int index, System.Text.Json.Nodes.JsonNode? item) { }
706706
public bool Remove(System.Text.Json.Nodes.JsonNode? item) { throw null; }
707707
public void RemoveAt(int index) { }
708+
public int RemoveAll(System.Func<System.Text.Json.Nodes.JsonNode?, bool> match) { throw null; }
709+
public void RemoveRange(int index, int count) { }
708710
void System.Collections.Generic.ICollection<System.Text.Json.Nodes.JsonNode?>.CopyTo(System.Text.Json.Nodes.JsonNode?[]? array, int index) { }
709711
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
710712
public override void WriteTo(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.JsonSerializerOptions? options = null) { }

src/libraries/System.Text.Json/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,4 +807,7 @@
807807
<data name="Arg_HTCapacityOverflow" xml:space="preserve">
808808
<value>Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table.</value>
809809
</data>
810+
<data name="Argument_InvalidOffLen" xml:space="preserve">
811+
<value>Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.</value>
812+
</data>
810813
</root>

src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.IList.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,78 @@ public void RemoveAt(int index)
114114
DetachParent(item);
115115
}
116116

117+
/// <summary>
118+
/// Removes all the elements that match the conditions defined by the specified predicate.
119+
/// </summary>
120+
/// <param name="match">The predicate that defines the conditions of the elements to remove.</param>
121+
/// <returns>The number of elements removed from the <see cref="JsonArray"/>.</returns>
122+
/// <exception cref="ArgumentNullException">
123+
/// <paramref name="match"/> is <see langword="null"/>.
124+
/// </exception>
125+
public int RemoveAll(Func<JsonNode?, bool> match)
126+
{
127+
if (match == null)
128+
{
129+
ThrowHelper.ThrowArgumentNullException(nameof(match));
130+
}
131+
132+
return List.RemoveAll(node =>
133+
{
134+
if (match(node))
135+
{
136+
DetachParent(node);
137+
return true;
138+
}
139+
else
140+
{
141+
return false;
142+
}
143+
});
144+
}
145+
146+
/// <summary>
147+
/// Removes a range of elements from the <see cref="JsonArray"/>.
148+
/// </summary>
149+
/// <param name="index">The zero-based starting index of the range of elements to remove.</param>
150+
/// <param name="count">The number of elements to remove.</param>
151+
/// <exception cref="ArgumentOutOfRangeException">
152+
/// <paramref name="index"/> or <paramref name="count"/> is less than 0.
153+
/// </exception>
154+
/// <exception cref="ArgumentException">
155+
/// <paramref name="index"/> and <paramref name="count"/> do not denote a valid range of elements in the <see cref="JsonArray"/>.
156+
/// </exception>
157+
public void RemoveRange(int index, int count)
158+
{
159+
if (index < 0)
160+
{
161+
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(nameof(index));
162+
}
163+
164+
if (count < 0)
165+
{
166+
ThrowHelper.ThrowArgumentOutOfRangeException_NeedNonNegNum(nameof(count));
167+
}
168+
169+
List<JsonNode?> list = List;
170+
171+
if (list.Count - index < count)
172+
{
173+
ThrowHelper.ThrowArgumentException_InvalidOffLen();
174+
}
175+
176+
if (count > 0)
177+
{
178+
for (int i = 0; i < count; i++)
179+
{
180+
DetachParent(list[index + i]);
181+
// There's no need to assign nulls because List<>.RemoveRange calls
182+
// Array.Clear on the removed partition.
183+
}
184+
185+
list.RemoveRange(index, count);
186+
}
187+
}
188+
117189
#region Explicit interface implementation
118190

119191
/// <summary>

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ public static void ThrowArgumentOutOfRangeException_JsonConverterFactory_TypeNot
6565
throw new ArgumentOutOfRangeException(nameof(typeToConvert), SR.Format(SR.SerializerConverterFactoryInvalidArgument, typeToConvert.FullName));
6666
}
6767

68+
[DoesNotReturn]
69+
public static void ThrowArgumentOutOfRangeException_NeedNonNegNum(string paramName)
70+
{
71+
throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Generic_MustBeNonNegative);
72+
}
73+
74+
[DoesNotReturn]
75+
public static void ThrowArgumentException_InvalidOffLen()
76+
{
77+
throw new ArgumentException(SR.Argument_InvalidOffLen);
78+
}
79+
6880
[DoesNotReturn]
6981
public static void ThrowArgumentException_ArrayTooSmall(string paramName)
7082
{

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,5 +768,126 @@ public static void ReplaceWithJsonElement(string json)
768768

769769
Assert.Equal($"[{json}]", array.ToJsonString());
770770
}
771+
772+
[Fact]
773+
public static void RemoveAll_InvalidValuesAndEmptyArray()
774+
{
775+
JsonArray testArray = new();
776+
Assert.Throws<ArgumentNullException>(() => testArray.RemoveAll(null!));
777+
testArray.RemoveAll(whatever => true); // RemoveAll on empty array always succeeds.
778+
}
779+
780+
[Fact]
781+
public static void RemoveAll_FreshlyDeserialized()
782+
{
783+
JsonArray testArray = JsonSerializer.Deserialize<JsonArray>("[1,2,3,4,5]");
784+
Assert.Equal(3, testArray.RemoveAll(val => val.GetValue<int>() % 2 == 1));
785+
JsonNodeTests.AssertDeepEqual(new JsonArray { 2, 4 }, testArray);
786+
}
787+
788+
[Theory]
789+
[InlineData(new int[] { 1, 1, 1, 1, 1 }, new int[] { 1, 1, 1, 1, 1 }, 0)]
790+
[InlineData(new int[] { 2, 1, 1, 1, 1 }, new int[] { 1, 1, 1, 1 }, 1)]
791+
[InlineData(new int[] { 1, 1, 1, 1, 2 }, new int[] { 1, 1, 1, 1 }, 1)]
792+
[InlineData(new int[] { 1, 1, 1, 2, 1 }, new int[] { 1, 1, 1, 1 }, 1)]
793+
public static void RemoveAll_SpecialValues(int[] original, int[] expected, int removed)
794+
{
795+
// This test is to ensure the O(n) scan algorithm of JsonArray.RemoveAll is correct.
796+
797+
JsonArray testArray = new(ToNodes(original));
798+
Assert.Equal(removed, testArray.RemoveAll(Filter));
799+
JsonNodeTests.AssertDeepEqual(new JsonArray(ToNodes(expected)), testArray);
800+
801+
bool Filter(JsonNode? v) => v.GetValue<int>() > 1;
802+
JsonNode?[] ToNodes(int[] values) => values.Select(v => JsonValue.Create(v)).ToArray();
803+
}
804+
805+
[Fact]
806+
public static void RemoveAll()
807+
{
808+
JsonArray testArray = [1, 2, 3, 4, 5, 7, 8, 8, 8];
809+
JsonNode[] nodes = testArray.ToArray();
810+
811+
// Correct amount of nodes are removed / remaining
812+
Assert.Equal(5, testArray.RemoveAll(Filter));
813+
Assert.Equal(4, testArray.Count);
814+
815+
// The order is preserved
816+
JsonNodeTests.AssertDeepEqual(new JsonArray { 1, 3, 5, 7 }, testArray);
817+
818+
// Node parents are correctly handled
819+
foreach (JsonNode node in nodes)
820+
{
821+
if (Filter(node))
822+
{
823+
Assert.Null(node.Parent);
824+
}
825+
else
826+
{
827+
Assert.Same(testArray, node.Parent);
828+
}
829+
}
830+
831+
static bool Filter(JsonNode node) => node.GetValue<int>() % 2 == 0;
832+
}
833+
834+
[Fact]
835+
public static void RemoveRange_InvalidAndSpecialValues()
836+
{
837+
JsonArray emptyArray = new();
838+
emptyArray.RemoveRange(0, 0);
839+
Assert.Throws<ArgumentException>(() => emptyArray.RemoveRange(0, 1));
840+
Assert.Throws<ArgumentException>(() => emptyArray.RemoveRange(1, 0));
841+
842+
JsonArray testArray = [1, 2, 3, 4, 5, 6, 7, 8];
843+
Assert.Throws<ArgumentOutOfRangeException>(() => testArray.RemoveRange(-1, 1));
844+
Assert.Throws<ArgumentOutOfRangeException>(() => testArray.RemoveRange(1, -1));
845+
Assert.Throws<ArgumentException>(() => testArray.RemoveRange(10, 1));
846+
Assert.Throws<ArgumentException>(() => testArray.RemoveRange(1, 10));
847+
}
848+
849+
[Fact]
850+
public static void RemoveRange()
851+
{
852+
JsonArray testArray = [1, 2, 3, 4, 5, 6, 7, 8];
853+
JsonNode[] nodes = testArray.ToArray();
854+
855+
const int RemoveStartIndex = 3;
856+
const int RemoveLength = 3;
857+
858+
testArray.RemoveRange(RemoveStartIndex, RemoveLength);
859+
Assert.Equal(8 - RemoveLength, testArray.Count);
860+
JsonNodeTests.AssertDeepEqual(new JsonArray { 1, 2, 3, 7, 8 }, testArray);
861+
862+
for (int i = 0; i < nodes.Length; i++)
863+
{
864+
if (IsIndexRemoved(i))
865+
{
866+
Assert.Null(nodes[i].Parent);
867+
}
868+
else
869+
{
870+
Assert.Same(testArray, nodes[i].Parent);
871+
}
872+
}
873+
874+
static bool IsIndexRemoved(int originalIndex) => originalIndex >= RemoveStartIndex &&
875+
originalIndex < RemoveStartIndex + RemoveLength;
876+
}
877+
878+
[Fact]
879+
public static void RemoveRange_FreshlyDeserialized()
880+
{
881+
Assert.Throws<ArgumentOutOfRangeException>(() => PrepareData().RemoveRange(-1, 1));
882+
Assert.Throws<ArgumentOutOfRangeException>(() => PrepareData().RemoveRange(1, -1));
883+
Assert.Throws<ArgumentException>(() => PrepareData().RemoveRange(10, 1));
884+
Assert.Throws<ArgumentException>(() => PrepareData().RemoveRange(1, 10));
885+
886+
JsonArray testArray = PrepareData();
887+
testArray.RemoveRange(2, 2);
888+
JsonNodeTests.AssertDeepEqual(new JsonArray { 1, 2, 5 }, testArray);
889+
890+
static JsonArray PrepareData() => JsonSerializer.Deserialize<JsonArray>("[1,2,3,4,5]");
891+
}
771892
}
772893
}

0 commit comments

Comments
 (0)