| File: System\Text\Json\Nodes\JsonNode.cs | Web Access |
| Project: src\runtime\src\libraries\System.Text.Json\src\System.Text.Json.csproj (System.Text.Json) |
// 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.Text.Json.Serialization.Metadata; namespace System.Text.Json.Nodes { /// <summary> /// The base class that represents a single node within a mutable JSON document. /// </summary> /// <seealso cref="JsonSerializerOptions.UnknownTypeHandling"/> to specify that a type /// declared as an <see cref="object"/> should be deserialized as a <see cref="JsonNode"/>. public abstract partial class JsonNode { // Default options instance used when calling built-in JsonNode converters. private protected static readonly JsonSerializerOptions s_defaultOptions = new(); private JsonNode? _parent; private JsonNodeOptions? _options; /// <summary> /// The underlying JsonElement if the node is backed by a JsonElement. /// </summary> internal virtual JsonElement? UnderlyingElement => null; /// <summary> /// Options to control the behavior. /// </summary> public JsonNodeOptions? Options { get { if (!_options.HasValue && Parent is not null) { // Remember the parent options; if node is re-parented later we still want to keep the // original options since they may have affected the way the node was created as is the case // with JsonObject's case-insensitive dictionary. _options = Parent.Options; } return _options; } } internal JsonNode(JsonNodeOptions? options = null) { _options = options; } /// <summary> /// Casts to the derived <see cref="JsonArray"/> type. /// </summary> /// <returns> /// A <see cref="JsonArray"/>. /// </returns> /// <exception cref="InvalidOperationException"> /// The node is not a <see cref="JsonArray"/>. /// </exception> public JsonArray AsArray() { JsonArray? jArray = this as JsonArray; if (jArray is null) { ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonArray)); } return jArray; } /// <summary> /// Casts to the derived <see cref="JsonObject"/> type. /// </summary> /// <returns> /// A <see cref="JsonObject"/>. /// </returns> /// <exception cref="InvalidOperationException"> /// The node is not a <see cref="JsonObject"/>. /// </exception> public JsonObject AsObject() { JsonObject? jObject = this as JsonObject; if (jObject is null) { ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonObject)); } return jObject; } /// <summary> /// Casts to the derived <see cref="JsonValue"/> type. /// </summary> /// <returns> /// A <see cref="JsonValue"/>. /// </returns> /// <exception cref="InvalidOperationException"> /// The node is not a <see cref="JsonValue"/>. /// </exception> public JsonValue AsValue() { JsonValue? jValue = this as JsonValue; if (jValue is null) { ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonValue)); } return jValue; } /// <summary> /// Gets the parent <see cref="JsonNode"/>. /// If there is no parent, <see langword="null"/> is returned. /// A parent can either be a <see cref="JsonObject"/> or a <see cref="JsonArray"/>. /// </summary> public JsonNode? Parent { get { return _parent; } internal set { _parent = value; } } /// <summary> /// Gets the JSON path. /// </summary> /// <returns>The JSON Path value.</returns> public unsafe string GetPath() { if (Parent is null) { return "$"; } var path = new ValueStringBuilder(stackalloc char[JsonConstants.StackallocCharThreshold]); path.Append('$'); GetPath(ref path, null); return path.ToString(); } internal abstract void GetPath(ref ValueStringBuilder path, JsonNode? child); /// <summary> /// Gets the root <see cref="JsonNode"/>. /// </summary> /// <remarks> /// The current node is returned if it is a root. /// </remarks> public JsonNode Root { get { JsonNode? parent = Parent; if (parent is null) { return this; } while (parent.Parent is not null) { parent = parent.Parent; } return parent; } } /// <summary> /// Gets the value for the current <see cref="JsonValue"/>. /// </summary> /// <typeparam name="T">The type of the value to obtain from the <see cref="JsonValue"/>.</typeparam> /// <returns>A value converted from the <see cref="JsonValue"/> instance.</returns> /// <remarks> /// {T} can be the type or base type of the underlying value. /// If the underlying value is a <see cref="JsonElement"/> then {T} can also be the type of any primitive /// value supported by current <see cref="JsonElement"/>. /// Specifying the <see cref="object"/> type for {T} will always succeed and return the underlying value as <see cref="object"/>.<br /> /// The underlying value of a <see cref="JsonValue"/> after deserialization is an instance of <see cref="JsonElement"/>, /// otherwise it's the value specified when the <see cref="JsonValue"/> was created. /// </remarks> /// <seealso cref="System.Text.Json.Nodes.JsonValue.TryGetValue"></seealso> /// <exception cref="FormatException"> /// The current <see cref="JsonNode"/> cannot be represented as a {T}. /// </exception> /// <exception cref="InvalidOperationException"> /// The current <see cref="JsonNode"/> is not a <see cref="JsonValue"/> or /// is not compatible with {T}. /// </exception> public virtual T GetValue<T>() => throw new InvalidOperationException(SR.Format(SR.NodeWrongType, nameof(JsonValue))); /// <summary> /// Gets or sets the element at the specified index. /// </summary> /// <param name="index">The zero-based index of the element to get or set.</param> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="index"/> is less than 0 or <paramref name="index"/> is greater than the number of properties. /// </exception> /// <exception cref="InvalidOperationException"> /// The current <see cref="JsonNode"/> is not a <see cref="JsonArray"/> or <see cref="JsonObject"/>. /// </exception> public JsonNode? this[int index] { get => GetItem(index); set => SetItem(index, value); } private protected virtual JsonNode? GetItem(int index) { ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonArray), nameof(JsonObject)); return null; } private protected virtual void SetItem(int index, JsonNode? node) => ThrowHelper.ThrowInvalidOperationException_NodeWrongType(nameof(JsonArray), nameof(JsonObject)); /// <summary> /// Gets or sets the element with the specified property name. /// If the property is not found, <see langword="null"/> is returned. /// </summary> /// <param name="propertyName">The name of the property to return.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="propertyName"/> is <see langword="null"/>. /// </exception> /// <exception cref="InvalidOperationException"> /// The current <see cref="JsonNode"/> is not a <see cref="JsonObject"/>. /// </exception> public JsonNode? this[string propertyName] { get { return AsObject().GetItem(propertyName); } set { AsObject().SetItem(propertyName, value); } } /// <summary> /// Creates a new instance of the <see cref="JsonNode"/>. All children nodes are recursively cloned. /// </summary> /// <returns>A new cloned instance of the current node.</returns> public JsonNode DeepClone() => DeepCloneCore(); internal abstract JsonNode DeepCloneCore(); /// <summary> /// Returns <see cref="JsonValueKind"/> of current instance. /// </summary> public JsonValueKind GetValueKind() => GetValueKindCore(); private protected abstract JsonValueKind GetValueKindCore(); /// <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? parentObject = _parent as JsonObject; if (parentObject is null) { ThrowHelper.ThrowInvalidOperationException_NodeParentWrongType(nameof(JsonObject)); } return parentObject.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? parentArray = _parent as JsonArray; if (parentArray is null) { ThrowHelper.ThrowInvalidOperationException_NodeParentWrongType(nameof(JsonArray)); } return parentArray.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; } else if (node2 is null) { return false; } return node1.DeepEqualsCore(node2); } internal abstract bool DeepEqualsCore(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) { JsonNode? node; switch (_parent) { case JsonObject jsonObject: node = ConvertFromValue(value); jsonObject.SetItem(GetPropertyName(), node); return; case JsonArray jsonArray: node = ConvertFromValue(value); jsonArray.SetItem(GetElementIndex(), node); return; } } internal void AssignParent(JsonNode parent) { if (Parent is not null) { ThrowHelper.ThrowInvalidOperationException_NodeAlreadyHasParent(); } JsonNode? p = parent; while (p is not null) { if (p == this) { ThrowHelper.ThrowInvalidOperationException_NodeCycleDetected(); } p = p.Parent; } Parent = parent; } /// <summary> /// Adaptation of the equivalent JsonValue.Create factory method extended /// to support arbitrary <see cref="JsonElement"/> and <see cref="JsonNode"/> values. /// TODO consider making public cf. https://github.com/dotnet/runtime/issues/70427 /// </summary> [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] internal static JsonNode? ConvertFromValue<T>(T? value, JsonNodeOptions? options = null) { if (value is null) { return null; } if (value is JsonNode node) { return node; } if (value is JsonElement element) { return JsonNodeConverter.Create(element, options); } var jsonTypeInfo = JsonSerializerOptions.Default.GetTypeInfo<T>(); return JsonValue.CreateFromTypeInfo(value, jsonTypeInfo, options); } } }