Skip to content

Commit dd079f5

Browse files
authored
Additional JsonNode functionality (#87381)
* add deep clone implementation * Add tests for DeepClone JsonObject method * add more tests * Add GetElementIndex, GetPropertyName api * Add ReplaceWith api * Add api description * remove unused code * add more tests * resolve comments * resolve comments. * resolve comments. * resolve comments. * add test cases * resolve comments. * resolve comments. * remove unused namespace. * resolve comments. * Resolve comments. * resolve comments. * fix failed test. * enhance comparision. * resolve comments. * resolve comments. * resolve comments. * resolve comments. * resolve comments. * add more test cases. * resolve comments. * resolve comments. * add test cases.
1 parent 318c0e6 commit dd079f5

File tree

13 files changed

+1011
-12
lines changed

13 files changed

+1011
-12
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ public void Clear() { }
660660
public bool Contains(System.Text.Json.Nodes.JsonNode? item) { throw null; }
661661
public static System.Text.Json.Nodes.JsonArray? Create(System.Text.Json.JsonElement element, System.Text.Json.Nodes.JsonNodeOptions? options = default(System.Text.Json.Nodes.JsonNodeOptions?)) { throw null; }
662662
public System.Collections.Generic.IEnumerator<System.Text.Json.Nodes.JsonNode?> GetEnumerator() { throw null; }
663+
public System.Collections.Generic.IEnumerable<T> GetValues<T>() { throw null; }
663664
public int IndexOf(System.Text.Json.Nodes.JsonNode? item) { throw null; }
664665
public void Insert(int index, System.Text.Json.Nodes.JsonNode? item) { }
665666
public bool Remove(System.Text.Json.Nodes.JsonNode? item) { throw null; }
@@ -679,8 +680,13 @@ internal JsonNode() { }
679680
public System.Text.Json.Nodes.JsonArray AsArray() { throw null; }
680681
public System.Text.Json.Nodes.JsonObject AsObject() { throw null; }
681682
public System.Text.Json.Nodes.JsonValue AsValue() { throw null; }
683+
public System.Text.Json.Nodes.JsonNode DeepClone() { throw null; }
684+
public static bool DeepEquals(System.Text.Json.Nodes.JsonNode? node1, System.Text.Json.Nodes.JsonNode? node2) { throw null; }
685+
public string GetPropertyName() { throw null; }
682686
public string GetPath() { throw null; }
683687
public virtual T GetValue<T>() { throw null; }
688+
public JsonValueKind GetValueKind() { throw null; }
689+
public int GetElementIndex() { throw null; }
684690
public static explicit operator bool (System.Text.Json.Nodes.JsonNode value) { throw null; }
685691
public static explicit operator byte (System.Text.Json.Nodes.JsonNode value) { throw null; }
686692
public static explicit operator char (System.Text.Json.Nodes.JsonNode value) { throw null; }
@@ -768,6 +774,10 @@ internal JsonNode() { }
768774
public static System.Text.Json.Nodes.JsonNode? Parse(System.ReadOnlySpan<byte> utf8Json, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?), System.Text.Json.JsonDocumentOptions documentOptions = default(System.Text.Json.JsonDocumentOptions)) { throw null; }
769775
public static System.Text.Json.Nodes.JsonNode? Parse([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("Json")] string json, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?), System.Text.Json.JsonDocumentOptions documentOptions = default(System.Text.Json.JsonDocumentOptions)) { throw null; }
770776
public static System.Text.Json.Nodes.JsonNode? Parse(ref System.Text.Json.Utf8JsonReader reader, System.Text.Json.Nodes.JsonNodeOptions? nodeOptions = default(System.Text.Json.Nodes.JsonNodeOptions?)) { throw null; }
777+
778+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Creating JsonValue instances with non-primitive types requires generating code at runtime.")]
779+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Creating JsonValue instances with non-primitive types is not compatible with trimming. It can result in non-primitive types being serialized, which may have their members trimmed.")]
780+
public void ReplaceWith<T>(T value) { throw null; }
771781
public string ToJsonString(System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
772782
public override string ToString() { throw null; }
773783
public abstract void WriteTo(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.JsonSerializerOptions? options = null);

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,6 @@ internal bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropert
287287
{
288288
CheckNotDisposed();
289289

290-
int matchIndex = isPropertyName ? index - DbRow.Size : index;
291-
292290
byte[]? otherUtf8TextArray = null;
293291

294292
int length = checked(otherText.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,86 @@ public JsonArray(params JsonNode?[] items) : base()
4848
InitializeFromArray(items);
4949
}
5050

51+
internal override JsonNode InternalDeepClone()
52+
{
53+
if (_jsonElement.HasValue)
54+
{
55+
return new JsonArray(_jsonElement.Value.Clone(), Options);
56+
}
57+
58+
List<JsonNode?> list = List;
59+
60+
var jsonArray = new JsonArray(Options)
61+
{
62+
_list = new List<JsonNode?>(list.Count)
63+
};
64+
65+
for (int i = 0; i < list.Count; i++)
66+
{
67+
JsonNode? item = list[i];
68+
if (item is null)
69+
{
70+
jsonArray.Add(null);
71+
}
72+
else
73+
{
74+
jsonArray.Add(item.DeepClone());
75+
}
76+
}
77+
78+
return jsonArray;
79+
}
80+
81+
internal override bool DeepEquals(JsonNode? node)
82+
{
83+
switch (node)
84+
{
85+
case null or JsonObject:
86+
return false;
87+
case JsonValue value:
88+
// JsonValueTrimmable/NonTrimmable can hold the array type so calling this method to continue the deep comparision.
89+
return value.DeepEquals(this);
90+
case JsonArray array:
91+
List<JsonNode?> currentList = List;
92+
List<JsonNode?> otherList = array.List;
93+
94+
if (currentList.Count != otherList.Count)
95+
{
96+
return false;
97+
}
98+
99+
for (int i = 0; i < currentList.Count; i++)
100+
{
101+
if (!DeepEquals(currentList[i], otherList[i]))
102+
{
103+
return false;
104+
}
105+
}
106+
107+
return true;
108+
default:
109+
Debug.Fail("Impossible case");
110+
return false;
111+
}
112+
}
113+
114+
internal int GetElementIndex(JsonNode? node)
115+
{
116+
return List.IndexOf(node);
117+
}
118+
119+
/// <summary>
120+
/// Returns enumerator that wraps calls to <see cref="JsonNode.GetValue{T}"/>.
121+
/// </summary>
122+
/// <typeparam name="T">The type of the value to obtain from the <see cref="JsonValue"/>.</typeparam>
123+
public IEnumerable<T> GetValues<T>()
124+
{
125+
foreach (JsonNode? item in List)
126+
{
127+
yield return item is null ? (T)(object?)null! : item.GetValue<T>();
128+
}
129+
}
130+
51131
private void InitializeFromArray(JsonNode?[] items)
52132
{
53133
var list = new List<JsonNode?>(items);

src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
56

67
namespace System.Text.Json.Nodes
78
{
@@ -50,12 +51,14 @@ internal JsonNode(JsonNodeOptions? options = null)
5051
/// </exception>
5152
public JsonArray AsArray()
5253
{
53-
if (this is JsonArray jArray)
54+
JsonArray? jArray = this as JsonArray;
55+
56+
if (jArray is null)
5457
{
55-
return jArray;
58+
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonArray));
5659
}
5760

58-
throw new InvalidOperationException(SR.Format(SR.NodeWrongType, nameof(JsonArray)));
61+
return jArray;
5962
}
6063

6164
/// <summary>
@@ -69,12 +72,14 @@ public JsonArray AsArray()
6972
/// </exception>
7073
public JsonObject AsObject()
7174
{
72-
if (this is JsonObject jObject)
75+
JsonObject? jObject = this as JsonObject;
76+
77+
if (jObject is null)
7378
{
74-
return jObject;
79+
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonObject));
7580
}
7681

77-
throw new InvalidOperationException(SR.Format(SR.NodeWrongType, nameof(JsonObject)));
82+
return jObject;
7883
}
7984

8085
/// <summary>
@@ -88,12 +93,14 @@ public JsonObject AsObject()
8893
/// </exception>
8994
public JsonValue AsValue()
9095
{
91-
if (this is JsonValue jValue)
96+
JsonValue? jValue = this as JsonValue;
97+
98+
if (jValue is null)
9299
{
93-
return jValue;
100+
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonValue));
94101
}
95102

96-
throw new InvalidOperationException(SR.Format(SR.NodeWrongType, nameof(JsonValue)));
103+
return jValue;
97104
}
98105

99106
/// <summary>
@@ -232,6 +239,107 @@ public JsonNode? this[string propertyName]
232239
}
233240
}
234241

242+
/// <summary>
243+
/// Creates a new instance of the <see cref="JsonNode"/>. All children nodes are recursively cloned.
244+
/// </summary>
245+
public JsonNode DeepClone()
246+
{
247+
return InternalDeepClone();
248+
}
249+
250+
internal abstract JsonNode InternalDeepClone();
251+
252+
/// <summary>
253+
/// Returns <see cref="JsonValueKind"/> of current instance.
254+
/// </summary>
255+
public JsonValueKind GetValueKind()
256+
{
257+
return this switch
258+
{
259+
JsonObject => JsonValueKind.Object,
260+
JsonArray => JsonValueKind.Array,
261+
_ => AsValue().GetInternalValueKind(),
262+
};
263+
}
264+
265+
/// <summary>
266+
/// Returns property name of the current node from the parent object.
267+
/// </summary>
268+
/// <exception cref="InvalidOperationException">
269+
/// The current parent is not a <see cref="JsonObject"/>.
270+
/// </exception>
271+
public string GetPropertyName()
272+
{
273+
JsonObject? jsonObject = _parent as JsonObject;
274+
275+
if (jsonObject is null)
276+
{
277+
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonObject));
278+
}
279+
280+
return jsonObject.GetPropertyName(this);
281+
}
282+
283+
/// <summary>
284+
/// Returns index of the current node from the parent <see cref="JsonArray" />.
285+
/// </summary>
286+
/// <exception cref="InvalidOperationException">
287+
/// The current parent is not a <see cref="JsonArray"/>.
288+
/// </exception>
289+
public int GetElementIndex()
290+
{
291+
JsonArray? jsonArray = _parent as JsonArray;
292+
293+
if (jsonArray is null)
294+
{
295+
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonArray));
296+
}
297+
298+
return jsonArray.GetElementIndex(this);
299+
}
300+
301+
/// <summary>
302+
/// Compares the values of two nodes, including the values of all descendant nodes.
303+
/// </summary>
304+
/// <param name="node1">The <see cref="JsonNode"/> to compare.</param>
305+
/// <param name="node2">The <see cref="JsonNode"/> to compare.</param>
306+
/// <returns><c>true</c> if the tokens are equal; otherwise <c>false</c>.</returns>
307+
public static bool DeepEquals(JsonNode? node1, JsonNode? node2)
308+
{
309+
if (node1 is null)
310+
{
311+
return node2 is null;
312+
}
313+
314+
return node1.DeepEquals(node2);
315+
}
316+
317+
internal abstract bool DeepEquals(JsonNode? node);
318+
319+
/// <summary>
320+
/// Replaces this node with a new value.
321+
/// </summary>
322+
/// <typeparam name="T">The type of value to be replaced.</typeparam>
323+
/// <param name="value">Value that replaces this node.</param>
324+
[RequiresUnreferencedCode(JsonValue.CreateUnreferencedCodeMessage)]
325+
[RequiresDynamicCode(JsonValue.CreateDynamicCodeMessage)]
326+
public void ReplaceWith<T>(T value)
327+
{
328+
switch (_parent)
329+
{
330+
case null:
331+
return;
332+
case JsonObject jsonObject:
333+
JsonValue? jsonValue = JsonValue.Create(value);
334+
jsonObject.SetItem(GetPropertyName(), jsonValue);
335+
return;
336+
case JsonArray jsonArray:
337+
JsonValue? jValue = JsonValue.Create(value);
338+
jsonArray.SetItem(GetElementIndex(), jValue);
339+
return;
340+
}
341+
}
342+
235343
internal void AssignParent(JsonNode parent)
236344
{
237345
if (Parent != null)

src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,46 @@ internal JsonObject(JsonElement element, JsonNodeOptions? options = null) : this
7070
_jsonElement = element;
7171
}
7272

73+
internal override JsonNode InternalDeepClone()
74+
{
75+
if (_jsonElement.HasValue)
76+
{
77+
return new JsonObject(_jsonElement!.Value.Clone(), Options);
78+
}
79+
80+
if (_dictionary is not null)
81+
{
82+
bool caseInsensitive = Options.HasValue ? Options.Value.PropertyNameCaseInsensitive : false;
83+
var jObject = new JsonObject(Options)
84+
{
85+
_dictionary = new JsonPropertyDictionary<JsonNode?>(caseInsensitive, _dictionary.Count)
86+
};
87+
88+
foreach (KeyValuePair<string, JsonNode?> item in _dictionary)
89+
{
90+
if (item.Value is not null)
91+
{
92+
jObject.Add(item.Key, item.Value.DeepClone());
93+
}
94+
else
95+
{
96+
jObject.Add(item.Key, null);
97+
}
98+
}
99+
return jObject;
100+
}
101+
102+
return new JsonObject(Options);
103+
}
104+
105+
internal string GetPropertyName(JsonNode? node)
106+
{
107+
InitializeIfRequired();
108+
Debug.Assert(_dictionary != null);
109+
KeyValuePair<string, JsonNode?>? item = _dictionary.FindValue(node);
110+
return item.HasValue ? item.Value.Key : string.Empty;
111+
}
112+
73113
/// <summary>
74114
/// Returns the value of a property with the specified name.
75115
/// </summary>
@@ -110,6 +150,43 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio
110150
}
111151
}
112152

153+
internal override bool DeepEquals(JsonNode? node)
154+
{
155+
switch (node)
156+
{
157+
case null or JsonArray:
158+
return false;
159+
case JsonValue value:
160+
// JsonValueTrimmable/NonTrimmable can hold the object type so calling this method to continue the deep comparision.
161+
return value.DeepEquals(this);
162+
case JsonObject jsonObject:
163+
InitializeIfRequired();
164+
jsonObject.InitializeIfRequired();
165+
Debug.Assert(_dictionary is not null);
166+
Debug.Assert(jsonObject._dictionary is not null);
167+
168+
if (_dictionary.Count != jsonObject._dictionary.Count)
169+
{
170+
return false;
171+
}
172+
173+
foreach (KeyValuePair<string, JsonNode?> item in this)
174+
{
175+
JsonNode? jsonNode = jsonObject._dictionary[item.Key];
176+
177+
if (!DeepEquals(item.Value, jsonNode))
178+
{
179+
return false;
180+
}
181+
}
182+
183+
return true;
184+
default:
185+
Debug.Fail("Impossible case");
186+
return false;
187+
}
188+
}
189+
113190
internal JsonNode? GetItem(string propertyName)
114191
{
115192
if (TryGetPropertyValue(propertyName, out JsonNode? value))

0 commit comments

Comments
 (0)