|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Converters;
using System.Threading;
namespace System.Text.Json.Nodes
{
/// <summary>
/// Represents a mutable JSON array.
/// </summary>
/// <remarks>
/// It is safe to perform multiple concurrent read operations on a <see cref="JsonArray"/>,
/// but issues can occur if the collection is modified while it's being read.
/// </remarks>
[DebuggerDisplay("JsonArray[{List.Count}]")]
[DebuggerTypeProxy(typeof(DebugView))]
public sealed partial class JsonArray : JsonNode
{
private JsonElement? _jsonElement;
private List<JsonNode?>? _list;
internal override JsonElement? UnderlyingElement => _jsonElement;
/// <summary>
/// Initializes a new instance of the <see cref="JsonArray"/> class that is empty.
/// </summary>
/// <param name="options">Options to control the behavior.</param>
public JsonArray(JsonNodeOptions? options = null) : base(options) { }
/// <summary>
/// Initializes a new instance of the <see cref="JsonArray"/> class that contains items from the specified params array.
/// </summary>
/// <param name="options">Options to control the behavior.</param>
/// <param name="items">The items to add to the new <see cref="JsonArray"/>.</param>
public JsonArray(JsonNodeOptions options, params JsonNode?[] items) : base(options)
{
InitializeFromArray(items);
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonArray"/> class that contains items from the specified params span.
/// </summary>
/// <param name="options">Options to control the behavior.</param>
/// <param name="items">The items to add to the new <see cref="JsonArray"/>.</param>
public JsonArray(JsonNodeOptions options, params ReadOnlySpan<JsonNode?> items) : base(options)
{
InitializeFromSpan(items);
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonArray"/> class that contains items from the specified array.
/// </summary>
/// <param name="items">The items to add to the new <see cref="JsonArray"/>.</param>
public JsonArray(params JsonNode?[] items) : base()
{
InitializeFromArray(items);
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonArray"/> class that contains items from the specified span.
/// </summary>
/// <param name="items">The items to add to the new <see cref="JsonArray"/>.</param>
public JsonArray(params ReadOnlySpan<JsonNode?> items) : base()
{
InitializeFromSpan(items);
}
private protected override JsonValueKind GetValueKindCore() => JsonValueKind.Array;
internal override JsonNode DeepCloneCore()
{
GetUnderlyingRepresentation(out List<JsonNode?>? list, out JsonElement? jsonElement);
if (list is null)
{
return jsonElement.HasValue
? new JsonArray(jsonElement.Value.Clone(), Options)
: new JsonArray(Options);
}
var jsonArray = new JsonArray(Options)
{
_list = new List<JsonNode?>(list.Count)
};
for (int i = 0; i < list.Count; i++)
{
jsonArray.Add(list[i]?.DeepCloneCore());
}
return jsonArray;
}
internal override bool DeepEqualsCore(JsonNode node)
{
switch (node)
{
case JsonObject:
return false;
case JsonValue value:
// JsonValue instances have special comparison semantics, dispatch to their implementation.
return value.DeepEqualsCore(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 an enumerable 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>
/// <returns>An enumerable iterating over values of the array.</returns>
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);
for (int i = 0; i < list.Count; i++)
{
list[i]?.AssignParent(this);
}
_list = list;
}
private void InitializeFromSpan(ReadOnlySpan<JsonNode?> items)
{
List<JsonNode?> list = new(items.Length);
#if NET8_0_OR_GREATER
list.AddRange(items);
#else
foreach (JsonNode? item in items)
{
list.Add(item);
}
#endif
for (int i = 0; i < list.Count; i++)
{
list[i]?.AssignParent(this);
}
_list = list;
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonArray"/> class that contains items from the specified <see cref="JsonElement"/>.
/// </summary>
/// <returns>
/// The new instance of the <see cref="JsonArray"/> class that contains items from the specified <see cref="JsonElement"/>.
/// </returns>
/// <param name="element">The <see cref="JsonElement"/>.</param>
/// <param name="options">Options to control the behavior.</param>
/// <exception cref="InvalidOperationException">
/// The <paramref name="element"/> is not a <see cref="JsonValueKind.Array"/>.
/// </exception>
public static JsonArray? Create(JsonElement element, JsonNodeOptions? options = null)
{
return element.ValueKind switch
{
JsonValueKind.Null => null,
JsonValueKind.Array => new JsonArray(element, options),
_ => throw new InvalidOperationException(SR.Format(SR.NodeElementWrongType, nameof(JsonValueKind.Array))),
};
}
internal JsonArray(JsonElement element, JsonNodeOptions? options = null) : base(options)
{
Debug.Assert(element.ValueKind == JsonValueKind.Array);
_jsonElement = element;
}
/// <summary>
/// Adds an object to the end of the <see cref="JsonArray"/>.
/// </summary>
/// <typeparam name="T">The type of object to be added.</typeparam>
/// <param name="value">
/// The object to be added to the end of the <see cref="JsonArray"/>.
/// </param>
[RequiresUnreferencedCode(JsonValue.CreateUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonValue.CreateDynamicCodeMessage)]
public void Add<T>(T? value)
{
JsonNode? nodeToAdd = ConvertFromValue(value, Options);
Add(nodeToAdd);
}
/// <summary>
/// Gets or creates the underlying list containing the element nodes of the array.
/// </summary>
private List<JsonNode?> List => _list ?? InitializeList();
private protected override JsonNode? GetItem(int index)
{
return List[index];
}
private protected override void SetItem(int index, JsonNode? value)
{
value?.AssignParent(this);
DetachParent(List[index]);
List[index] = value;
}
internal override void GetPath(ref ValueStringBuilder path, JsonNode? child)
{
Parent?.GetPath(ref path, this);
if (child != null)
{
int index = List.IndexOf(child);
Debug.Assert(index >= 0);
path.Append('[');
#if NET
Span<char> chars = stackalloc char[JsonConstants.MaximumFormatUInt32Length];
bool formatted = ((uint)index).TryFormat(chars, out int charsWritten);
Debug.Assert(formatted);
path.Append(chars.Slice(0, charsWritten));
#else
path.Append(index.ToString());
#endif
path.Append(']');
}
}
/// <inheritdoc/>
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
if (writer is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(writer));
}
GetUnderlyingRepresentation(out List<JsonNode?>? list, out JsonElement? jsonElement);
if (list is null && jsonElement.HasValue)
{
jsonElement.Value.WriteTo(writer);
}
else
{
writer.WriteStartArray();
foreach (JsonNode? element in List)
{
if (element is null)
{
writer.WriteNullValue();
}
else
{
element.WriteTo(writer, options);
}
}
writer.WriteEndArray();
}
}
private List<JsonNode?> InitializeList()
{
GetUnderlyingRepresentation(out List<JsonNode?>? list, out JsonElement? jsonElement);
if (list is null)
{
if (jsonElement.HasValue)
{
JsonElement jElement = jsonElement.Value;
Debug.Assert(jElement.ValueKind == JsonValueKind.Array);
list = new List<JsonNode?>(jElement.GetArrayLength());
foreach (JsonElement element in jElement.EnumerateArray())
{
JsonNode? node = JsonNodeConverter.Create(element, Options);
node?.AssignParent(this);
list.Add(node);
}
}
else
{
list = new();
}
// Ensure _jsonElement is written to after _list
_list = list;
Interlocked.MemoryBarrier();
_jsonElement = null;
}
return list;
}
/// <summary>
/// Provides a coherent view of the underlying representation of the current node.
/// The jsonElement value should be consumed if and only if the list value is null.
/// </summary>
private void GetUnderlyingRepresentation(out List<JsonNode?>? list, out JsonElement? jsonElement)
{
// Because JsonElement cannot be read atomically there might be torn reads,
// however the order of read/write operations guarantees that that's only
// possible if the value of _list is non-null.
jsonElement = _jsonElement;
Interlocked.MemoryBarrier();
list = _list;
}
[ExcludeFromCodeCoverage] // Justification = "Design-time"
private sealed class DebugView
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly JsonArray _node;
public DebugView(JsonArray node)
{
_node = node;
}
public string Json => _node.ToJsonString();
public string Path => _node.GetPath();
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
private DebugViewItem[] Items
{
get
{
DebugViewItem[] properties = new DebugViewItem[_node.List.Count];
for (int i = 0; i < _node.List.Count; i++)
{
properties[i].Value = _node.List[i];
}
return properties;
}
}
[DebuggerDisplay("{Display,nq}")]
private struct DebugViewItem
{
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public JsonNode? Value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string Display
{
get
{
if (Value == null)
{
return $"null";
}
if (Value is JsonValue)
{
return Value.ToJsonString();
}
if (Value is JsonObject jsonObject)
{
return $"JsonObject[{jsonObject.Count}]";
}
JsonArray jsonArray = (JsonArray)Value;
return $"JsonArray[{jsonArray.List.Count}]";
}
}
}
}
}
}
|