Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional JsonNode functionality #87381

Merged
merged 33 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b40325a
add deep clone implementation
RaymondHuy Jun 5, 2023
111341c
Add tests for DeepClone JsonObject method
RaymondHuy Jun 6, 2023
742275c
add more tests
RaymondHuy Jun 7, 2023
b1c3bc1
Add GetElementIndex, GetPropertyName api
RaymondHuy Jun 11, 2023
5acdc1e
Add ReplaceWith api
RaymondHuy Jun 11, 2023
ef5e20f
Add api description
RaymondHuy Jun 11, 2023
726e52f
remove unused code
RaymondHuy Jun 11, 2023
1c9bdd0
add more tests
RaymondHuy Jun 11, 2023
b95a736
resolve comments
RaymondHuy Jun 12, 2023
4b40026
resolve comments.
RaymondHuy Jun 22, 2023
aa0e65f
resolve comments.
RaymondHuy Jun 22, 2023
c135fca
resolve comments.
RaymondHuy Jun 22, 2023
b1f2501
add test cases
RaymondHuy Jun 22, 2023
31f974d
resolve comments.
RaymondHuy Jun 22, 2023
6aee45e
Merge branch 'master' into issue-56592
RaymondHuy Jun 23, 2023
8b79cdf
resolve comments.
RaymondHuy Jun 24, 2023
413e9e1
remove unused namespace.
RaymondHuy Jun 24, 2023
9a5b8eb
resolve comments.
RaymondHuy Jun 24, 2023
02ad18e
Resolve comments.
RaymondHuy Jun 24, 2023
0db1b9c
resolve comments.
RaymondHuy Jun 24, 2023
6f4297b
fix failed test.
RaymondHuy Jun 24, 2023
f2d55c0
enhance comparision.
RaymondHuy Jun 24, 2023
a7973f9
resolve comments.
RaymondHuy Jun 24, 2023
332d337
resolve comments.
RaymondHuy Jun 24, 2023
742314e
Merge branch 'master' into issue-56592
RaymondHuy Jun 25, 2023
ca2798b
resolve comments.
RaymondHuy Jun 26, 2023
0d6460e
resolve comments.
RaymondHuy Jun 26, 2023
e382464
resolve comments.
RaymondHuy Jun 27, 2023
d8281cc
add more test cases.
RaymondHuy Jun 27, 2023
20822c2
resolve comments.
RaymondHuy Jun 27, 2023
e44ee7e
resolve comments.
RaymondHuy Jun 27, 2023
58dbb25
Merge branch 'master' into issue-56592
RaymondHuy Jun 27, 2023
963f7bb
add test cases.
RaymondHuy Jun 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ public void Clear() { }
public bool Contains(System.Text.Json.Nodes.JsonNode? item) { throw null; }
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; }
public System.Collections.Generic.IEnumerator<System.Text.Json.Nodes.JsonNode?> GetEnumerator() { throw null; }
public System.Collections.Generic.IEnumerable<T> GetValues<T>() { throw null; }
public int IndexOf(System.Text.Json.Nodes.JsonNode? item) { throw null; }
public void Insert(int index, System.Text.Json.Nodes.JsonNode? item) { }
public bool Remove(System.Text.Json.Nodes.JsonNode? item) { throw null; }
Expand All @@ -679,8 +680,13 @@ internal JsonNode() { }
public System.Text.Json.Nodes.JsonArray AsArray() { throw null; }
public System.Text.Json.Nodes.JsonObject AsObject() { throw null; }
public System.Text.Json.Nodes.JsonValue AsValue() { throw null; }
public System.Text.Json.Nodes.JsonNode DeepClone() { throw null; }
public static bool DeepEquals(System.Text.Json.Nodes.JsonNode? node1, System.Text.Json.Nodes.JsonNode? node2) { throw null; }
public string GetPropertyName() { throw null; }
public string GetPath() { throw null; }
public virtual T GetValue<T>() { throw null; }
public JsonValueKind GetValueKind() { throw null; }
public int GetElementIndex() { throw null; }
public static explicit operator bool (System.Text.Json.Nodes.JsonNode value) { throw null; }
public static explicit operator byte (System.Text.Json.Nodes.JsonNode value) { throw null; }
public static explicit operator char (System.Text.Json.Nodes.JsonNode value) { throw null; }
Expand Down Expand Up @@ -768,6 +774,10 @@ internal JsonNode() { }
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; }
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; }
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; }

[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Creating JsonValue instances with non-primitive types requires generating code at runtime.")]
[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.")]
public void ReplaceWith<T>(T value) { throw null; }
public string ToJsonString(System.Text.Json.JsonSerializerOptions? options = null) { throw null; }
public override string ToString() { throw null; }
public abstract void WriteTo(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.JsonSerializerOptions? options = null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,6 @@ internal bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropert
{
CheckNotDisposed();

int matchIndex = isPropertyName ? index - DbRow.Size : index;
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved

byte[]? otherUtf8TextArray = null;

int length = checked(otherText.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,86 @@ public JsonArray(params JsonNode?[] items) : base()
InitializeFromArray(items);
}

internal override JsonNode InternalDeepClone()
{
if (_jsonElement.HasValue)
{
return new JsonArray(_jsonElement.Value.Clone(), Options);
}

List<JsonNode?> list = List;

var jsonArray = new JsonArray(Options)
{
_list = new List<JsonNode?>(list.Count)
};

for (int i = 0; i < list.Count; i++)
{
JsonNode? item = list[i];
if (item is null)
{
jsonArray.Add(null);
}
else
{
jsonArray.Add(item.DeepClone());
}
}

return jsonArray;
}

internal override bool DeepEquals(JsonNode? node)
{
switch (node)
{
case null or JsonObject:
return false;
case JsonValue value:
// JsonValueTrimmable/NonTrimmable can hold the array type so calling this method to continue the deep comparision.
return value.DeepEquals(this);
case JsonArray array:
List<JsonNode?> currentList = List;
List<JsonNode?> otherList = array.List;

if (currentList.Count != otherList.Count)
{
return false;
}

for (int i = 0; i < currentList.Count; i++)
{
if (!DeepEquals(currentList[i], otherList[i]))
{
return false;
}
}

return true;
default:
Debug.Fail("Impossible case");
return false;
}
}

internal int GetElementIndex(JsonNode? node)
{
return List.IndexOf(node);
}

/// <summary>
/// Returns enumerator that wraps calls to <see cref="JsonNode.GetValue{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the value to obtain from the <see cref="JsonValue"/>.</typeparam>
public IEnumerable<T> GetValues<T>()
{
foreach (JsonNode? item in List)
{
yield return item is null ? (T)(object?)null! : item.GetValue<T>();
}
}

private void InitializeFromArray(JsonNode?[] items)
{
var list = new List<JsonNode?>(items);
Expand Down
126 changes: 117 additions & 9 deletions src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace System.Text.Json.Nodes
{
Expand Down Expand Up @@ -50,12 +51,14 @@ internal JsonNode(JsonNodeOptions? options = null)
/// </exception>
public JsonArray AsArray()
{
if (this is JsonArray jArray)
JsonArray? jArray = this as JsonArray;

if (jArray is null)
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
{
return jArray;
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonArray));
}

throw new InvalidOperationException(SR.Format(SR.NodeWrongType, nameof(JsonArray)));
return jArray;
}

/// <summary>
Expand All @@ -69,12 +72,14 @@ public JsonArray AsArray()
/// </exception>
public JsonObject AsObject()
{
if (this is JsonObject jObject)
JsonObject? jObject = this as JsonObject;

if (jObject is null)
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
{
return jObject;
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonObject));
}

throw new InvalidOperationException(SR.Format(SR.NodeWrongType, nameof(JsonObject)));
return jObject;
}

/// <summary>
Expand All @@ -88,12 +93,14 @@ public JsonObject AsObject()
/// </exception>
public JsonValue AsValue()
{
if (this is JsonValue jValue)
JsonValue? jValue = this as JsonValue;

if (jValue is null)
{
return jValue;
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonValue));
}

throw new InvalidOperationException(SR.Format(SR.NodeWrongType, nameof(JsonValue)));
return jValue;
}

/// <summary>
Expand Down Expand Up @@ -232,6 +239,107 @@ public JsonNode? this[string propertyName]
}
}

/// <summary>
/// Creates a new instance of the <see cref="JsonNode"/>. All children nodes are recursively cloned.
/// </summary>
public JsonNode DeepClone()
{
return InternalDeepClone();
}

internal abstract JsonNode InternalDeepClone();

/// <summary>
/// Returns <see cref="JsonValueKind"/> of current instance.
/// </summary>
public JsonValueKind GetValueKind()
{
return this switch
{
JsonObject => JsonValueKind.Object,
JsonArray => JsonValueKind.Array,
_ => AsValue().GetInternalValueKind(),
};
}

/// <summary>
/// Returns property name of the current node from the parent object.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The current parent is not a <see cref="JsonObject"/>.
/// </exception>
public string GetPropertyName()
{
JsonObject? jsonObject = _parent as JsonObject;

if (jsonObject is null)
{
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonObject));
}

return jsonObject.GetPropertyName(this);
}

/// <summary>
/// Returns index of the current node from the parent <see cref="JsonArray" />.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The current parent is not a <see cref="JsonArray"/>.
/// </exception>
public int GetElementIndex()
{
JsonArray? jsonArray = _parent as JsonArray;

if (jsonArray is null)
{
ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonArray));
}

return jsonArray.GetElementIndex(this);
}

/// <summary>
/// Compares the values of two nodes, including the values of all descendant nodes.
/// </summary>
/// <param name="node1">The <see cref="JsonNode"/> to compare.</param>
/// <param name="node2">The <see cref="JsonNode"/> to compare.</param>
/// <returns><c>true</c> if the tokens are equal; otherwise <c>false</c>.</returns>
public static bool DeepEquals(JsonNode? node1, JsonNode? node2)
{
if (node1 is null)
{
return node2 is null;
}

return node1.DeepEquals(node2);
}

internal abstract bool DeepEquals(JsonNode? node);

/// <summary>
/// Replaces this node with a new value.
/// </summary>
/// <typeparam name="T">The type of value to be replaced.</typeparam>
/// <param name="value">Value that replaces this node.</param>
[RequiresUnreferencedCode(JsonValue.CreateUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonValue.CreateDynamicCodeMessage)]
public void ReplaceWith<T>(T value)
{
switch (_parent)
{
case null:
return;
case JsonObject jsonObject:
JsonValue? jsonValue = JsonValue.Create(value);
jsonObject.SetItem(GetPropertyName(), jsonValue);
return;
case JsonArray jsonArray:
JsonValue? jValue = JsonValue.Create(value);
jsonArray.SetItem(GetElementIndex(), jValue);
return;
}
}

internal void AssignParent(JsonNode parent)
{
if (Parent != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,46 @@ internal JsonObject(JsonElement element, JsonNodeOptions? options = null) : this
_jsonElement = element;
}

internal override JsonNode InternalDeepClone()
{
if (_jsonElement.HasValue)
{
return new JsonObject(_jsonElement!.Value.Clone(), Options);
}

if (_dictionary is not null)
{
bool caseInsensitive = Options.HasValue ? Options.Value.PropertyNameCaseInsensitive : false;
var jObject = new JsonObject(Options)
{
_dictionary = new JsonPropertyDictionary<JsonNode?>(caseInsensitive, _dictionary.Count)
};

foreach (KeyValuePair<string, JsonNode?> item in _dictionary)
{
if (item.Value is not null)
{
jObject.Add(item.Key, item.Value.DeepClone());
}
else
{
jObject.Add(item.Key, null);
}
}
return jObject;
}

return new JsonObject(Options);
}

internal string GetPropertyName(JsonNode? node)
{
InitializeIfRequired();
Debug.Assert(_dictionary != null);
KeyValuePair<string, JsonNode?>? item = _dictionary.FindValue(node);
return item.HasValue ? item.Value.Key : string.Empty;
}

/// <summary>
/// Returns the value of a property with the specified name.
/// </summary>
Expand Down Expand Up @@ -110,6 +150,43 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio
}
}

internal override bool DeepEquals(JsonNode? node)
{
switch (node)
{
case null or JsonArray:
return false;
case JsonValue value:
// JsonValueTrimmable/NonTrimmable can hold the object type so calling this method to continue the deep comparision.
return value.DeepEquals(this);
case JsonObject jsonObject:
InitializeIfRequired();
jsonObject.InitializeIfRequired();
Debug.Assert(_dictionary is not null);
Debug.Assert(jsonObject._dictionary is not null);

if (_dictionary.Count != jsonObject._dictionary.Count)
{
return false;
}

foreach (KeyValuePair<string, JsonNode?> item in this)
{
JsonNode? jsonNode = jsonObject._dictionary[item.Key];

if (!DeepEquals(item.Value, jsonNode))
{
return false;
}
}

return true;
default:
Debug.Fail("Impossible case");
return false;
}
}

internal JsonNode? GetItem(string propertyName)
{
if (TryGetPropertyValue(propertyName, out JsonNode? value))
Expand Down
Loading