diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
index aee388056c8cc8..9f88d70f9860bd 100644
--- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj
+++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
@@ -75,6 +75,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
+
@@ -380,12 +381,6 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
-
-
-
-
-
-
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs
index 5a9b20ad023b80..2fcc1d79e3eae4 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs
@@ -118,6 +118,17 @@ internal JsonTokenType GetJsonTokenType(int index)
return _parsedData.GetJsonTokenType(index);
}
+ internal bool ValueIsEscaped(int index, bool isPropertyName)
+ {
+ CheckNotDisposed();
+
+ int matchIndex = isPropertyName ? index - DbRow.Size : index;
+ DbRow row = _parsedData.Get(matchIndex);
+ Debug.Assert(!isPropertyName || row.TokenType is JsonTokenType.PropertyName);
+
+ return row.HasComplexChildren;
+ }
+
internal int GetArrayLength(int index)
{
CheckNotDisposed();
@@ -363,6 +374,16 @@ internal string GetNameOfPropertyValue(int index)
return GetString(index - DbRow.Size, JsonTokenType.PropertyName)!;
}
+ internal ReadOnlySpan GetPropertyNameRaw(int index)
+ {
+ CheckNotDisposed();
+
+ DbRow row = _parsedData.Get(index - DbRow.Size);
+ Debug.Assert(row.TokenType is JsonTokenType.PropertyName);
+
+ return _utf8Json.Span.Slice(row.Location, row.SizeOrLength);
+ }
+
internal bool TryGetValue(int index, [NotNullWhen(true)] out byte[]? value)
{
CheckNotDisposed();
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
index 1ca7fff9f7e333..f5d2ff099e7233 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
@@ -1164,6 +1164,13 @@ internal string GetPropertyName()
return _parent.GetNameOfPropertyValue(_idx);
}
+ internal ReadOnlySpan GetPropertyNameRaw()
+ {
+ CheckValidInstance();
+
+ return _parent.GetPropertyNameRaw(_idx);
+ }
+
///
/// Gets the original input data backing this value, returning it as a .
///
@@ -1194,6 +1201,154 @@ internal string GetPropertyRawText()
return _parent.GetPropertyRawValueAsString(_idx);
}
+ internal bool ValueIsEscaped
+ {
+ // TODO make public https://github.com/dotnet/runtime/issues/77666
+ get
+ {
+ CheckValidInstance();
+
+ return _parent.ValueIsEscaped(_idx, isPropertyName: false);
+ }
+ }
+
+ internal ReadOnlySpan ValueSpan
+ {
+ // TODO make public https://github.com/dotnet/runtime/issues/77666
+ get
+ {
+ CheckValidInstance();
+
+ return _parent.GetRawValue(_idx, includeQuotes: false).Span;
+ }
+ }
+
+ // TODO make public https://github.com/dotnet/runtime/issues/33388
+ internal static bool DeepEquals(JsonElement left, JsonElement right)
+ {
+ Debug.Assert(left._parent != null);
+ Debug.Assert(right._parent != null);
+
+ JsonValueKind kind = left.ValueKind;
+ if (kind != right.ValueKind)
+ {
+ return false;
+ }
+
+ switch (kind)
+ {
+ case JsonValueKind.Null or JsonValueKind.False or JsonValueKind.True:
+ return true;
+
+ case JsonValueKind.Number:
+ // JSON numbers are equal if their raw representations are equal.
+ return left.GetRawValue().Span.SequenceEqual(right.GetRawValue().Span);
+
+ case JsonValueKind.String:
+ if (right.ValueIsEscaped)
+ {
+ if (left.ValueIsEscaped)
+ {
+ // Both values are escaped, force an allocation to unescape the RHS.
+ return left.ValueEquals(right.GetString());
+ }
+
+ // Swap values so that unescaping is handled by the LHS.
+ (left, right) = (right, left);
+ }
+
+ return left.ValueEquals(right.ValueSpan);
+
+ case JsonValueKind.Array:
+ ArrayEnumerator rightArrayEnumerator = right.EnumerateArray();
+ foreach (JsonElement leftElement in left.EnumerateArray())
+ {
+ if (!rightArrayEnumerator.MoveNext() || !DeepEquals(leftElement, rightArrayEnumerator.Current))
+ {
+ return false;
+ }
+ }
+
+ return !rightArrayEnumerator.MoveNext();
+
+ default:
+ Debug.Assert(kind is JsonValueKind.Object);
+ ObjectEnumerator leftObjectEnumerator = left.EnumerateObject();
+ ObjectEnumerator rightObjectEnumerator = right.EnumerateObject();
+
+ // Two JSON objects are considered equal if they define the same set of properties.
+ // Start optimistically with sequential comparison, but fall back to unordered
+ // comparison as soon as a mismatch is encountered.
+
+ while (leftObjectEnumerator.MoveNext())
+ {
+ if (!rightObjectEnumerator.MoveNext())
+ {
+ return false;
+ }
+
+ JsonProperty leftProp = leftObjectEnumerator.Current;
+ JsonProperty rightProp = rightObjectEnumerator.Current;
+
+ if (!NameEquals(leftProp, rightProp))
+ {
+ // We have our first mismatch, fall back to unordered comparison.
+ return UnorderedObjectDeepEquals(leftObjectEnumerator, rightObjectEnumerator);
+ }
+
+ if (!DeepEquals(leftProp.Value, rightProp.Value))
+ {
+ return false;
+ }
+ }
+
+ return !rightObjectEnumerator.MoveNext();
+
+ static bool UnorderedObjectDeepEquals(ObjectEnumerator left, ObjectEnumerator right)
+ {
+ Dictionary rightElements = new(StringComparer.Ordinal);
+ do
+ {
+ JsonProperty prop = right.Current;
+ rightElements.TryAdd(prop.Name, prop.Value);
+ }
+ while (right.MoveNext());
+
+ int leftCount = 0;
+ do
+ {
+ JsonProperty prop = left.Current;
+ if (!rightElements.TryGetValue(prop.Name, out JsonElement rightElement) || !DeepEquals(prop.Value, rightElement))
+ {
+ return false;
+ }
+
+ leftCount++;
+ }
+ while (left.MoveNext());
+
+ return leftCount == rightElements.Count;
+ }
+
+ static bool NameEquals(JsonProperty left, JsonProperty right)
+ {
+ if (right.NameIsEscaped)
+ {
+ if (left.NameIsEscaped)
+ {
+ // Both values are escaped, force an allocation to unescape the RHS.
+ return left.NameEquals(right.Name);
+ }
+
+ // Swap values so that unescaping is handled by the LHS
+ (left, right) = (right, left);
+ }
+
+ return left.NameEquals(right.NameSpan);
+ }
+ }
+ }
+
///
/// Compares to the string value of this element.
///
@@ -1292,6 +1447,13 @@ internal bool TextEqualsHelper(ReadOnlySpan text, bool isPropertyName)
return _parent.TextEquals(_idx, text, isPropertyName);
}
+ internal bool ValueIsEscapedHelper(bool isPropertyName)
+ {
+ CheckValidInstance();
+
+ return _parent.ValueIsEscaped(_idx, isPropertyName);
+ }
+
///
/// Write the element into the provided writer as a JSON value.
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs
index 9cad409a32b7a5..cac951cd9df918 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs
@@ -18,10 +18,9 @@ public readonly struct JsonProperty
public JsonElement Value { get; }
private string? _name { get; }
- internal JsonProperty(JsonElement value, string? name = null)
+ internal JsonProperty(JsonElement value)
{
Value = value;
- _name = name;
}
///
@@ -94,6 +93,10 @@ internal bool EscapedNameEquals(ReadOnlySpan utf8Text)
return Value.TextEqualsHelper(utf8Text, isPropertyName: true, shouldUnescape: false);
}
+ // TODO make public https://github.com/dotnet/runtime/issues/77666
+ internal bool NameIsEscaped => Value.ValueIsEscapedHelper(isPropertyName: true);
+ internal ReadOnlySpan NameSpan => Value.GetPropertyNameRaw();
+
///
/// Write the property into the provided writer as a named JSON object property.
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs
index 0c95ee94fe896d..815a3348d4b041 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs
@@ -23,6 +23,8 @@ public sealed partial class JsonArray : JsonNode
private JsonElement? _jsonElement;
private List? _list;
+ internal override JsonElement? UnderlyingElement => _jsonElement;
+
///
/// Initializes a new instance of the class that is empty.
///
@@ -93,11 +95,11 @@ internal override JsonNode DeepCloneCore()
return jsonArray;
}
- internal override bool DeepEqualsCore(JsonNode? node)
+ internal override bool DeepEqualsCore(JsonNode node)
{
switch (node)
{
- case null or JsonObject:
+ case JsonObject:
return false;
case JsonValue value:
// JsonValue instances have special comparison semantics, dispatch to their implementation.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs
index 0304718c81aa25..34b12052d31d18 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.To.cs
@@ -44,13 +44,12 @@ public override string ToString()
// Special case for string; don't quote it.
if (this is JsonValue)
{
- if (this is JsonValue jsonString)
+ if (this is JsonValuePrimitive jsonString)
{
return jsonString.Value;
}
- if (this is JsonValue jsonElement &&
- jsonElement.Value.ValueKind == JsonValueKind.String)
+ if (this is JsonValueOfElement { Value.ValueKind: JsonValueKind.String } jsonElement)
{
return jsonElement.Value.GetString()!;
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs
index 70b80505b7a7d6..ba611933eeee96 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs
@@ -14,9 +14,17 @@ namespace System.Text.Json.Nodes
/// declared as an should be deserialized as a .
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;
+ ///
+ /// The underlying JsonElement if the node is backed by a JsonElement.
+ ///
+ internal virtual JsonElement? UnderlyingElement => null;
+
///
/// Options to control the behavior.
///
@@ -300,11 +308,15 @@ public static bool DeepEquals(JsonNode? node1, JsonNode? node2)
{
return node2 is null;
}
+ else if (node2 is null)
+ {
+ return false;
+ }
return node1.DeepEqualsCore(node2);
}
- internal abstract bool DeepEqualsCore(JsonNode? node);
+ internal abstract bool DeepEqualsCore(JsonNode node);
///
/// Replaces this node with a new value.
@@ -375,7 +387,7 @@ internal void AssignParent(JsonNode parent)
}
var jsonTypeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(T));
- return new JsonValueCustomized(value, jsonTypeInfo, options);
+ return JsonValue.CreateFromTypeInfo(value, jsonTypeInfo, options);
}
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs
index 8756b93366f90c..3de8d933a1b070 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IDictionary.cs
@@ -25,6 +25,11 @@ public partial class JsonObject : IDictionary
///
public void Add(string propertyName, JsonNode? value)
{
+ if (propertyName is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
+ }
+
Dictionary.Add(propertyName, value);
value?.AssignParent(this);
}
@@ -74,7 +79,15 @@ public void Clear()
///
/// is .
///
- public bool ContainsKey(string propertyName) => Dictionary.ContainsKey(propertyName);
+ public bool ContainsKey(string propertyName)
+ {
+ if (propertyName is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
+ }
+
+ return Dictionary.ContainsKey(propertyName);
+ }
///
/// Gets the number of elements contained in .
@@ -180,7 +193,15 @@ public bool Remove(string propertyName)
///
/// is .
///
- bool IDictionary.TryGetValue(string propertyName, out JsonNode? jsonNode) => Dictionary.TryGetValue(propertyName, out jsonNode);
+ bool IDictionary.TryGetValue(string propertyName, out JsonNode? jsonNode)
+ {
+ if (propertyName is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
+ }
+
+ return Dictionary.TryGetValue(propertyName, out jsonNode);
+ }
///
/// Returns .
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IList.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IList.cs
index d7032fd5125eda..372eccdcfe016a 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IList.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.IList.cs
@@ -23,6 +23,11 @@ public partial class JsonObject : IList>
/// already has a parent.
public void SetAt(int index, string propertyName, JsonNode? value)
{
+ if (propertyName is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
+ }
+
OrderedDictionary dictionary = Dictionary;
KeyValuePair existing = dictionary.GetAt(index);
dictionary.SetAt(index, propertyName, value);
@@ -48,7 +53,15 @@ public void SetAt(int index, JsonNode? value)
/// The property name to locate.
/// The index of if found; otherwise, -1.
/// is null.
- public int IndexOf(string propertyName) => Dictionary.IndexOf(propertyName);
+ public int IndexOf(string propertyName)
+ {
+ if (propertyName is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
+ }
+
+ return Dictionary.IndexOf(propertyName);
+ }
/// Inserts a property into the object at the specified index.
/// The zero-based index at which the property should be inserted.
@@ -59,6 +72,11 @@ public void SetAt(int index, JsonNode? value)
/// is less than 0 or greater than .
public void Insert(int index, string propertyName, JsonNode? value)
{
+ if (propertyName is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
+ }
+
Dictionary.Insert(index, propertyName, value);
value?.AssignParent(this);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs
index 8b32efee897e2e..667a7626b120b4 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonObject.cs
@@ -20,6 +20,8 @@ public sealed partial class JsonObject : JsonNode
{
private JsonElement? _jsonElement;
+ internal override JsonElement? UnderlyingElement => _jsonElement;
+
///
/// Initializes a new instance of the class that is empty.
///
@@ -116,8 +118,15 @@ internal string GetPropertyName(JsonNode? node)
///
/// if a property with the specified name was found; otherwise, .
///
- public bool TryGetPropertyValue(string propertyName, out JsonNode? jsonNode) =>
- Dictionary.TryGetValue(propertyName, out jsonNode);
+ public bool TryGetPropertyValue(string propertyName, out JsonNode? jsonNode)
+ {
+ if (propertyName is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(propertyName));
+ }
+
+ return Dictionary.TryGetValue(propertyName, out jsonNode);
+ }
///
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
@@ -158,11 +167,11 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio
private protected override JsonValueKind GetValueKindCore() => JsonValueKind.Object;
- internal override bool DeepEqualsCore(JsonNode? node)
+ internal override bool DeepEqualsCore(JsonNode node)
{
switch (node)
{
- case null or JsonArray:
+ case JsonArray:
return false;
case JsonValue value:
// JsonValue instances have special comparison semantics, dispatch to their implementation.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.CreateOverloads.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.CreateOverloads.cs
index 824869192765ff..439274348f4d3f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.CreateOverloads.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.CreateOverloads.cs
@@ -14,7 +14,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(bool value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.BooleanConverter);
+ public static JsonValue Create(bool value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.BooleanConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -22,7 +22,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(bool? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.BooleanConverter) : null;
+ public static JsonValue? Create(bool? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.BooleanConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -30,7 +30,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(byte value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.ByteConverter);
+ public static JsonValue Create(byte value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.ByteConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -38,7 +38,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(byte? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.ByteConverter) : null;
+ public static JsonValue? Create(byte? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.ByteConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -46,7 +46,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(char value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.CharConverter);
+ public static JsonValue Create(char value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.CharConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -54,7 +54,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(char? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.CharConverter) : null;
+ public static JsonValue? Create(char? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.CharConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -62,7 +62,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(DateTime value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.DateTimeConverter);
+ public static JsonValue Create(DateTime value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.DateTimeConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -70,7 +70,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(DateTime? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.DateTimeConverter) : null;
+ public static JsonValue? Create(DateTime? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.DateTimeConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -78,7 +78,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(DateTimeOffset value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.DateTimeOffsetConverter);
+ public static JsonValue Create(DateTimeOffset value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.DateTimeOffsetConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -86,7 +86,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(DateTimeOffset? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.DateTimeOffsetConverter) : null;
+ public static JsonValue? Create(DateTimeOffset? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.DateTimeOffsetConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -94,7 +94,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(decimal value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.DecimalConverter);
+ public static JsonValue Create(decimal value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.DecimalConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -102,7 +102,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(decimal? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.DecimalConverter) : null;
+ public static JsonValue? Create(decimal? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.DecimalConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -110,7 +110,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(double value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.DoubleConverter);
+ public static JsonValue Create(double value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.DoubleConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -118,7 +118,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(double? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.DoubleConverter) : null;
+ public static JsonValue? Create(double? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.DoubleConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -126,7 +126,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(Guid value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.GuidConverter);
+ public static JsonValue Create(Guid value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.GuidConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -134,7 +134,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(Guid? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.GuidConverter) : null;
+ public static JsonValue? Create(Guid? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.GuidConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -142,7 +142,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(short value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.Int16Converter);
+ public static JsonValue Create(short value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.Int16Converter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -150,7 +150,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(short? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.Int16Converter) : null;
+ public static JsonValue? Create(short? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.Int16Converter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -158,7 +158,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(int value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.Int32Converter);
+ public static JsonValue Create(int value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.Int32Converter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -166,7 +166,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(int? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.Int32Converter) : null;
+ public static JsonValue? Create(int? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.Int32Converter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -174,7 +174,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(long value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.Int64Converter);
+ public static JsonValue Create(long value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.Int64Converter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -182,7 +182,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(long? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.Int64Converter) : null;
+ public static JsonValue? Create(long? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.Int64Converter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -191,7 +191,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[CLSCompliantAttribute(false)]
- public static JsonValue Create(sbyte value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.SByteConverter);
+ public static JsonValue Create(sbyte value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.SByteConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -200,7 +200,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[CLSCompliantAttribute(false)]
- public static JsonValue? Create(sbyte? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.SByteConverter) : null;
+ public static JsonValue? Create(sbyte? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.SByteConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -208,7 +208,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue Create(float value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.SingleConverter);
+ public static JsonValue Create(float value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.SingleConverter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -216,7 +216,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(float? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.SingleConverter) : null;
+ public static JsonValue? Create(float? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.SingleConverter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -225,7 +225,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[return: NotNullIfNotNull(nameof(value))]
- public static JsonValue? Create(string? value, JsonNodeOptions? options = null) => value != null ? new JsonValuePrimitive(value, JsonMetadataServices.StringConverter) : null;
+ public static JsonValue? Create(string? value, JsonNodeOptions? options = null) => value != null ? new JsonValuePrimitive(value, JsonMetadataServices.StringConverter!, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -234,7 +234,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[CLSCompliantAttribute(false)]
- public static JsonValue Create(ushort value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.UInt16Converter);
+ public static JsonValue Create(ushort value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.UInt16Converter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -243,7 +243,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[CLSCompliantAttribute(false)]
- public static JsonValue? Create(ushort? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.UInt16Converter) : null;
+ public static JsonValue? Create(ushort? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.UInt16Converter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -252,7 +252,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[CLSCompliantAttribute(false)]
- public static JsonValue Create(uint value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.UInt32Converter);
+ public static JsonValue Create(uint value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.UInt32Converter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -261,7 +261,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[CLSCompliantAttribute(false)]
- public static JsonValue? Create(uint? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.UInt32Converter) : null;
+ public static JsonValue? Create(uint? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.UInt32Converter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -270,7 +270,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[CLSCompliantAttribute(false)]
- public static JsonValue Create(ulong value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.UInt64Converter);
+ public static JsonValue Create(ulong value, JsonNodeOptions? options = null) => new JsonValuePrimitive(value, JsonMetadataServices.UInt64Converter, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -279,7 +279,7 @@ public partial class JsonValue
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
[CLSCompliantAttribute(false)]
- public static JsonValue? Create(ulong? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.UInt64Converter) : null;
+ public static JsonValue? Create(ulong? value, JsonNodeOptions? options = null) => value.HasValue ? new JsonValuePrimitive(value.Value, JsonMetadataServices.UInt64Converter, options) : null;
///
/// Initializes a new instance of the class that contains the specified value.
@@ -287,17 +287,7 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(JsonElement value, JsonNodeOptions? options = null)
- {
- if (value.ValueKind == JsonValueKind.Null)
- {
- return null;
- }
-
- VerifyJsonElementIsNotArrayOrObject(ref value);
-
- return new JsonValuePrimitive(value, JsonMetadataServices.JsonElementConverter);
- }
+ public static JsonValue? Create(JsonElement value, JsonNodeOptions? options = null) => JsonValue.CreateFromElement(ref value, options);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -305,22 +295,6 @@ public partial class JsonValue
/// The underlying value of the new instance.
/// Options to control the behavior.
/// The new instance of the class that contains the specified value.
- public static JsonValue? Create(JsonElement? value, JsonNodeOptions? options = null)
- {
- if (value == null)
- {
- return null;
- }
-
- JsonElement element = value.Value;
- if (element.ValueKind == JsonValueKind.Null)
- {
- return null;
- }
-
- VerifyJsonElementIsNotArrayOrObject(ref element);
-
- return new JsonValuePrimitive(element, JsonMetadataServices.JsonElementConverter);
- }
+ public static JsonValue? Create(JsonElement? value, JsonNodeOptions? options = null) => value is JsonElement element ? JsonValue.CreateFromElement(ref element, options) : null;
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.cs
index 5aeb3300590569..09c21dbd5bb16e 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace System.Text.Json.Nodes
@@ -16,7 +17,24 @@ public abstract partial class JsonValue : JsonNode
internal const string CreateUnreferencedCodeMessage = "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.";
internal const string CreateDynamicCodeMessage = "Creating JsonValue instances with non-primitive types requires generating code at runtime.";
- private protected JsonValue(JsonNodeOptions? options = null) : base(options) { }
+ private protected JsonValue(JsonNodeOptions? options) : base(options) { }
+
+ ///
+ /// Tries to obtain the current JSON value and returns a value that indicates whether the operation succeeded.
+ ///
+ ///
+ /// {T} can be the type or base type of the underlying value.
+ /// If the underlying value is a then {T} can also be the type of any primitive
+ /// value supported by current .
+ /// Specifying the type for {T} will always succeed and return the underlying value as .
+ /// The underlying value of a after deserialization is an instance of ,
+ /// otherwise it's the value specified when the was created.
+ ///
+ ///
+ /// The type of value to obtain.
+ /// When this method returns, contains the parsed value.
+ /// if the value can be successfully obtained; otherwise, .
+ public abstract bool TryGetValue([NotNullWhen(true)] out T? value);
///
/// Initializes a new instance of the class that contains the specified value.
@@ -37,20 +55,18 @@ private protected JsonValue(JsonNodeOptions? options = null) : base(options) { }
return null;
}
- if (value is JsonElement element)
+ if (value is JsonNode)
{
- if (element.ValueKind is JsonValueKind.Null)
- {
- return null;
- }
-
- VerifyJsonElementIsNotArrayOrObject(ref element);
+ ThrowHelper.ThrowArgumentException_NodeValueNotAllowed(nameof(value));
+ }
- return new JsonValuePrimitive(element, JsonMetadataServices.JsonElementConverter, options);
+ if (value is JsonElement element)
+ {
+ return CreateFromElement(ref element, options);
}
var jsonTypeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(T));
- return new JsonValueCustomized(value, jsonTypeInfo, options);
+ return CreateFromTypeInfo(value, jsonTypeInfo, options);
}
///
@@ -76,51 +92,107 @@ private protected JsonValue(JsonNodeOptions? options = null) : base(options) { }
return null;
}
- if (value is JsonElement element)
+ if (value is JsonNode)
+ {
+ ThrowHelper.ThrowArgumentException_NodeValueNotAllowed(nameof(value));
+ }
+
+ jsonTypeInfo.EnsureConfigured();
+
+ if (value is JsonElement element && jsonTypeInfo.EffectiveConverter.IsInternalConverter)
{
- if (element.ValueKind is JsonValueKind.Null)
+ return CreateFromElement(ref element, options);
+ }
+
+ return CreateFromTypeInfo(value, jsonTypeInfo, options);
+ }
+
+ internal override bool DeepEqualsCore(JsonNode otherNode)
+ {
+ if (GetValueKind() != otherNode.GetValueKind())
+ {
+ return false;
+ }
+
+ // Fall back to slow path that converts the nodes to JsonElement.
+ JsonElement thisElement = ToJsonElement(this, out JsonDocument? thisDocument);
+ JsonElement otherElement = ToJsonElement(otherNode, out JsonDocument? otherDocument);
+ try
+ {
+ return JsonElement.DeepEquals(thisElement, otherElement);
+ }
+ finally
+ {
+ thisDocument?.Dispose();
+ otherDocument?.Dispose();
+ }
+
+ static JsonElement ToJsonElement(JsonNode node, out JsonDocument? backingDocument)
+ {
+ if (node.UnderlyingElement is { } element)
{
- return null;
+ backingDocument = null;
+ return element;
}
- VerifyJsonElementIsNotArrayOrObject(ref element);
- }
+ Utf8JsonWriter writer = Utf8JsonWriterCache.RentWriterAndBuffer(
+ options: default,
+ JsonSerializerOptions.BufferSizeDefault,
+ out PooledByteBufferWriter output);
- jsonTypeInfo.EnsureConfigured();
- return new JsonValueCustomized(value, jsonTypeInfo, options);
+ try
+ {
+ node.WriteTo(writer);
+ writer.Flush();
+ Utf8JsonReader reader = new(output.WrittenMemory.Span);
+ backingDocument = JsonDocument.ParseValue(ref reader);
+ return backingDocument.RootElement;
+ }
+ finally
+ {
+ Utf8JsonWriterCache.ReturnWriterAndBuffer(writer, output);
+ }
+ }
}
- internal override void GetPath(ref ValueStringBuilder path, JsonNode? child)
+ internal sealed override void GetPath(ref ValueStringBuilder path, JsonNode? child)
{
Debug.Assert(child == null);
Parent?.GetPath(ref path, this);
}
- ///
- /// Tries to obtain the current JSON value and returns a value that indicates whether the operation succeeded.
- ///
- ///
- /// {T} can be the type or base type of the underlying value.
- /// If the underlying value is a then {T} can also be the type of any primitive
- /// value supported by current .
- /// Specifying the type for {T} will always succeed and return the underlying value as .
- /// The underlying value of a after deserialization is an instance of ,
- /// otherwise it's the value specified when the was created.
- ///
- ///
- /// The type of value to obtain.
- /// When this method returns, contains the parsed value.
- /// if the value can be successfully obtained; otherwise, .
- public abstract bool TryGetValue([NotNullWhen(true)] out T? value);
+ internal static JsonValue CreateFromTypeInfo(T value, JsonTypeInfo jsonTypeInfo, JsonNodeOptions? options = null)
+ {
+ Debug.Assert(jsonTypeInfo.IsConfigured);
+ Debug.Assert(value != null);
+
+ if (JsonValue.TypeIsSupportedPrimitive &&
+ jsonTypeInfo is { EffectiveConverter.IsInternalConverter: true } &&
+ (jsonTypeInfo.EffectiveNumberHandling & JsonNumberHandling.WriteAsString) is 0)
+ {
+ // If the type is using the built-in converter for a known primitive,
+ // switch to the more efficient JsonValuePrimitive implementation.
+ return new JsonValuePrimitive(value, jsonTypeInfo.EffectiveConverter, options);
+ }
+
+ return new JsonValueCustomized(value, jsonTypeInfo, options);
+ }
- private static void VerifyJsonElementIsNotArrayOrObject(ref JsonElement element)
+ internal static JsonValue? CreateFromElement(ref readonly JsonElement element, JsonNodeOptions? options = null)
{
+ if (element.ValueKind is JsonValueKind.Null)
+ {
+ return null;
+ }
+
// Force usage of JsonArray and JsonObject instead of supporting those in an JsonValue.
if (element.ValueKind is JsonValueKind.Object or JsonValueKind.Array)
{
ThrowHelper.ThrowInvalidOperationException_NodeElementCannotBeObjectOrArray();
}
+
+ return new JsonValueOfElement(element, options);
}
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfElement.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfElement.cs
new file mode 100644
index 00000000000000..ddccceb8250330
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfElement.cs
@@ -0,0 +1,207 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace System.Text.Json.Nodes
+{
+ ///
+ /// Defines a primitive JSON value that is wrapping a .
+ ///
+ internal sealed class JsonValueOfElement : JsonValue
+ {
+ public JsonValueOfElement(JsonElement value, JsonNodeOptions? options) : base(value, options)
+ {
+ Debug.Assert(value.ValueKind is JsonValueKind.False or JsonValueKind.True or JsonValueKind.Number or JsonValueKind.String);
+ }
+
+ internal override JsonElement? UnderlyingElement => Value;
+ internal override JsonNode DeepCloneCore() => new JsonValueOfElement(Value.Clone(), Options);
+ private protected override JsonValueKind GetValueKindCore() => Value.ValueKind;
+
+ internal override bool DeepEqualsCore(JsonNode otherNode)
+ {
+ if (otherNode.UnderlyingElement is JsonElement otherElement)
+ {
+ return JsonElement.DeepEquals(Value, otherElement);
+ }
+
+ if (otherNode is JsonValue)
+ {
+ // Dispatch to the other value in case it knows
+ // how to convert JsonElement to its own type.
+ return otherNode.DeepEqualsCore(this);
+ }
+
+ return base.DeepEqualsCore(otherNode);
+ }
+
+ public override TypeToConvert GetValue()
+ {
+ if (!TryGetValue(out TypeToConvert? value))
+ {
+ ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(Value.ValueKind, typeof(TypeToConvert));
+ }
+
+ return value;
+ }
+
+ public override bool TryGetValue([NotNullWhen(true)] out TypeToConvert value)
+ {
+ bool success;
+
+ if (Value is TypeToConvert element)
+ {
+ value = element;
+ return true;
+ }
+
+ switch (Value.ValueKind)
+ {
+ case JsonValueKind.Number:
+ if (typeof(TypeToConvert) == typeof(int) || typeof(TypeToConvert) == typeof(int?))
+ {
+ success = Value.TryGetInt32(out int result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(long) || typeof(TypeToConvert) == typeof(long?))
+ {
+ success = Value.TryGetInt64(out long result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(double) || typeof(TypeToConvert) == typeof(double?))
+ {
+ success = Value.TryGetDouble(out double result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(short) || typeof(TypeToConvert) == typeof(short?))
+ {
+ success = Value.TryGetInt16(out short result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(decimal) || typeof(TypeToConvert) == typeof(decimal?))
+ {
+ success = Value.TryGetDecimal(out decimal result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(byte) || typeof(TypeToConvert) == typeof(byte?))
+ {
+ success = Value.TryGetByte(out byte result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(float) || typeof(TypeToConvert) == typeof(float?))
+ {
+ success = Value.TryGetSingle(out float result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(uint) || typeof(TypeToConvert) == typeof(uint?))
+ {
+ success = Value.TryGetUInt32(out uint result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(ushort) || typeof(TypeToConvert) == typeof(ushort?))
+ {
+ success = Value.TryGetUInt16(out ushort result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(ulong) || typeof(TypeToConvert) == typeof(ulong?))
+ {
+ success = Value.TryGetUInt64(out ulong result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(sbyte) || typeof(TypeToConvert) == typeof(sbyte?))
+ {
+ success = Value.TryGetSByte(out sbyte result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+ break;
+
+ case JsonValueKind.String:
+ if (typeof(TypeToConvert) == typeof(string))
+ {
+ string? result = Value.GetString();
+ Debug.Assert(result != null);
+ value = (TypeToConvert)(object)result;
+ return true;
+ }
+
+ if (typeof(TypeToConvert) == typeof(DateTime) || typeof(TypeToConvert) == typeof(DateTime?))
+ {
+ success = Value.TryGetDateTime(out DateTime result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(DateTimeOffset) || typeof(TypeToConvert) == typeof(DateTimeOffset?))
+ {
+ success = Value.TryGetDateTimeOffset(out DateTimeOffset result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(Guid) || typeof(TypeToConvert) == typeof(Guid?))
+ {
+ success = Value.TryGetGuid(out Guid result);
+ value = (TypeToConvert)(object)result;
+ return success;
+ }
+
+ if (typeof(TypeToConvert) == typeof(char) || typeof(TypeToConvert) == typeof(char?))
+ {
+ string? result = Value.GetString();
+ Debug.Assert(result != null);
+ if (result.Length == 1)
+ {
+ value = (TypeToConvert)(object)result[0];
+ return true;
+ }
+ }
+ break;
+
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ if (typeof(TypeToConvert) == typeof(bool) || typeof(TypeToConvert) == typeof(bool?))
+ {
+ value = (TypeToConvert)(object)Value.GetBoolean();
+ return true;
+ }
+ break;
+ }
+
+ value = default!;
+ return false;
+ }
+
+ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
+ {
+ if (writer is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(writer));
+ }
+
+ Value.WriteTo(writer);
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfT.cs
index 8067c321e0db43..5ae4d062da1318 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfT.cs
@@ -12,16 +12,11 @@ internal abstract class JsonValue : JsonValue
{
internal readonly TValue Value; // keep as a field for direct access to avoid copies
- protected JsonValue(TValue value, JsonNodeOptions? options = null) : base(options)
+ protected JsonValue(TValue value, JsonNodeOptions? options) : base(options)
{
Debug.Assert(value != null);
Debug.Assert(value is not JsonElement or JsonElement { ValueKind: not JsonValueKind.Null });
-
- if (value is JsonNode)
- {
- ThrowHelper.ThrowArgumentException_NodeValueNotAllowed(nameof(value));
- }
-
+ Debug.Assert(value is not JsonNode);
Value = value;
}
@@ -33,15 +28,11 @@ public override T GetValue()
return returnValue;
}
- if (Value is JsonElement)
- {
- return ConvertJsonElement();
- }
-
// Currently we do not support other conversions.
// Generics (and also boxing) do not support standard cast operators say from 'long' to 'int',
// so attempting to cast here would throw InvalidCastException.
- throw new InvalidOperationException(SR.Format(SR.NodeUnableToConvert, Value!.GetType(), typeof(T)));
+ ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvert(typeof(TValue), typeof(T));
+ return default!;
}
public override bool TryGetValue([NotNullWhen(true)] out T value)
@@ -53,11 +44,6 @@ public override bool TryGetValue([NotNullWhen(true)] out T value)
return true;
}
- if (Value is JsonElement)
- {
- return TryConvertJsonElement(out value);
- }
-
// Currently we do not support other conversions.
// Generics (and also boxing) do not support standard cast operators say from 'long' to 'int',
// so attempting to cast here would throw InvalidCastException.
@@ -65,324 +51,77 @@ public override bool TryGetValue([NotNullWhen(true)] out T value)
return false;
}
- private protected sealed override JsonValueKind GetValueKindCore()
+ ///
+ /// Whether is a built-in type that admits primitive JsonValue representation.
+ ///
+ internal static bool TypeIsSupportedPrimitive => s_valueKind.HasValue;
+ private static readonly JsonValueKind? s_valueKind = DetermineValueKindForType(typeof(TValue));
+
+ ///
+ /// Determines the JsonValueKind for the value of a built-in type.
+ ///
+ private protected static JsonValueKind DetermineValueKind(TValue value)
{
- if (Value is JsonElement element)
- {
- return element.ValueKind;
- }
+ Debug.Assert(s_valueKind is not null, "Should only be invoked for types that are supported primitives.");
- Utf8JsonWriter writer = Utf8JsonWriterCache.RentWriterAndBuffer(default, JsonSerializerOptions.BufferSizeDefault, out PooledByteBufferWriter output);
- try
- {
- WriteTo(writer);
- writer.Flush();
- return JsonElement.ParseValue(output.WrittenMemory.Span, options: default).ValueKind;
- }
- finally
+ if (value is bool boolean)
{
- Utf8JsonWriterCache.ReturnWriterAndBuffer(writer, output);
+ // Boolean requires special handling since kind varies by value.
+ return boolean ? JsonValueKind.True : JsonValueKind.False;
}
+
+ return s_valueKind.Value;
}
- internal sealed override bool DeepEqualsCore(JsonNode? otherNode)
+ ///
+ /// Precomputes the JsonValueKind for a given built-in type where possible.
+ ///
+ private static JsonValueKind? DetermineValueKindForType(Type type)
{
- if (otherNode is null)
+ if (type.IsEnum)
{
- return false;
+ return null; // Can vary depending on converter configuration and value.
}
- if (Value is JsonElement thisElement && otherNode is JsonValue { Value: JsonElement otherElement })
+ if (Nullable.GetUnderlyingType(type) is Type underlyingType)
{
- if (thisElement.ValueKind != otherElement.ValueKind)
- {
- return false;
- }
-
- switch (thisElement.ValueKind)
- {
- case JsonValueKind.Null:
- case JsonValueKind.True:
- case JsonValueKind.False:
- return true;
-
- case JsonValueKind.String:
- return thisElement.ValueEquals(otherElement.GetString());
- case JsonValueKind.Number:
- return thisElement.GetRawValue().Span.SequenceEqual(otherElement.GetRawValue().Span);
- default:
- Debug.Fail("Object and Array JsonElements cannot be contained in JsonValue.");
- return false;
- }
+ // Because JsonNode excludes null values, we can identify with the value kind of the underlying type.
+ return DetermineValueKindForType(underlyingType);
}
- using PooledByteBufferWriter thisOutput = WriteToPooledBuffer(this);
- using PooledByteBufferWriter otherOutput = WriteToPooledBuffer(otherNode);
- return thisOutput.WrittenMemory.Span.SequenceEqual(otherOutput.WrittenMemory.Span);
-
- static PooledByteBufferWriter WriteToPooledBuffer(
- JsonNode node,
- JsonSerializerOptions? options = null,
- JsonWriterOptions writerOptions = default,
- int bufferSize = JsonSerializerOptions.BufferSizeDefault)
+ if (type == typeof(DateTime) || type == typeof(DateTimeOffset) || type == typeof(TimeSpan) ||
+#if NET
+ type == typeof(DateOnly) || type == typeof(TimeOnly) ||
+#endif
+ type == typeof(Guid) || type == typeof(Uri) || type == typeof(Version))
{
- var bufferWriter = new PooledByteBufferWriter(bufferSize);
- using var writer = new Utf8JsonWriter(bufferWriter, writerOptions);
- node.WriteTo(writer, options);
- return bufferWriter;
+ return JsonValueKind.String;
}
- }
- internal TypeToConvert ConvertJsonElement()
- {
- JsonElement element = (JsonElement)(object)Value!;
-
- switch (element.ValueKind)
+#if NET
+ if (type == typeof(Half) || type == typeof(UInt128) || type == typeof(Int128))
{
- case JsonValueKind.Number:
- if (typeof(TypeToConvert) == typeof(int) || typeof(TypeToConvert) == typeof(int?))
- {
- return (TypeToConvert)(object)element.GetInt32();
- }
-
- if (typeof(TypeToConvert) == typeof(long) || typeof(TypeToConvert) == typeof(long?))
- {
- return (TypeToConvert)(object)element.GetInt64();
- }
-
- if (typeof(TypeToConvert) == typeof(double) || typeof(TypeToConvert) == typeof(double?))
- {
- return (TypeToConvert)(object)element.GetDouble();
- }
-
- if (typeof(TypeToConvert) == typeof(short) || typeof(TypeToConvert) == typeof(short?))
- {
- return (TypeToConvert)(object)element.GetInt16();
- }
-
- if (typeof(TypeToConvert) == typeof(decimal) || typeof(TypeToConvert) == typeof(decimal?))
- {
- return (TypeToConvert)(object)element.GetDecimal();
- }
-
- if (typeof(TypeToConvert) == typeof(byte) || typeof(TypeToConvert) == typeof(byte?))
- {
- return (TypeToConvert)(object)element.GetByte();
- }
-
- if (typeof(TypeToConvert) == typeof(float) || typeof(TypeToConvert) == typeof(float?))
- {
- return (TypeToConvert)(object)element.GetSingle();
- }
-
- if (typeof(TypeToConvert) == typeof(uint) || typeof(TypeToConvert) == typeof(uint?))
- {
- return (TypeToConvert)(object)element.GetUInt32();
- }
-
- if (typeof(TypeToConvert) == typeof(ushort) || typeof(TypeToConvert) == typeof(ushort?))
- {
- return (TypeToConvert)(object)element.GetUInt16();
- }
-
- if (typeof(TypeToConvert) == typeof(ulong) || typeof(TypeToConvert) == typeof(ulong?))
- {
- return (TypeToConvert)(object)element.GetUInt64();
- }
-
- if (typeof(TypeToConvert) == typeof(sbyte) || typeof(TypeToConvert) == typeof(sbyte?))
- {
- return (TypeToConvert)(object)element.GetSByte();
- }
- break;
-
- case JsonValueKind.String:
- if (typeof(TypeToConvert) == typeof(string))
- {
- return (TypeToConvert)(object)element.GetString()!;
- }
-
- if (typeof(TypeToConvert) == typeof(DateTime) || typeof(TypeToConvert) == typeof(DateTime?))
- {
- return (TypeToConvert)(object)element.GetDateTime();
- }
-
- if (typeof(TypeToConvert) == typeof(DateTimeOffset) || typeof(TypeToConvert) == typeof(DateTimeOffset?))
- {
- return (TypeToConvert)(object)element.GetDateTimeOffset();
- }
-
- if (typeof(TypeToConvert) == typeof(Guid) || typeof(TypeToConvert) == typeof(Guid?))
- {
- return (TypeToConvert)(object)element.GetGuid();
- }
-
- if (typeof(TypeToConvert) == typeof(char) || typeof(TypeToConvert) == typeof(char?))
- {
- string? str = element.GetString();
- Debug.Assert(str != null);
- if (str.Length == 1)
- {
- return (TypeToConvert)(object)str[0];
- }
- }
- break;
-
- case JsonValueKind.True:
- case JsonValueKind.False:
- if (typeof(TypeToConvert) == typeof(bool) || typeof(TypeToConvert) == typeof(bool?))
- {
- return (TypeToConvert)(object)element.GetBoolean();
- }
- break;
+ return JsonValueKind.Number;
}
-
- throw new InvalidOperationException(SR.Format(SR.NodeUnableToConvertElement,
- element.ValueKind,
- typeof(TypeToConvert)));
- }
-
- internal bool TryConvertJsonElement([NotNullWhen(true)] out TypeToConvert result)
- {
- bool success;
-
- JsonElement element = (JsonElement)(object)Value!;
-
- switch (element.ValueKind)
+#endif
+ return Type.GetTypeCode(type) switch
{
- case JsonValueKind.Number:
- if (typeof(TypeToConvert) == typeof(int) || typeof(TypeToConvert) == typeof(int?))
- {
- success = element.TryGetInt32(out int value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(long) || typeof(TypeToConvert) == typeof(long?))
- {
- success = element.TryGetInt64(out long value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(double) || typeof(TypeToConvert) == typeof(double?))
- {
- success = element.TryGetDouble(out double value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(short) || typeof(TypeToConvert) == typeof(short?))
- {
- success = element.TryGetInt16(out short value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(decimal) || typeof(TypeToConvert) == typeof(decimal?))
- {
- success = element.TryGetDecimal(out decimal value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(byte) || typeof(TypeToConvert) == typeof(byte?))
- {
- success = element.TryGetByte(out byte value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(float) || typeof(TypeToConvert) == typeof(float?))
- {
- success = element.TryGetSingle(out float value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(uint) || typeof(TypeToConvert) == typeof(uint?))
- {
- success = element.TryGetUInt32(out uint value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(ushort) || typeof(TypeToConvert) == typeof(ushort?))
- {
- success = element.TryGetUInt16(out ushort value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(ulong) || typeof(TypeToConvert) == typeof(ulong?))
- {
- success = element.TryGetUInt64(out ulong value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(sbyte) || typeof(TypeToConvert) == typeof(sbyte?))
- {
- success = element.TryGetSByte(out sbyte value);
- result = (TypeToConvert)(object)value;
- return success;
- }
- break;
-
- case JsonValueKind.String:
- if (typeof(TypeToConvert) == typeof(string))
- {
- string? strResult = element.GetString();
- Debug.Assert(strResult != null);
- result = (TypeToConvert)(object)strResult;
- return true;
- }
-
- if (typeof(TypeToConvert) == typeof(DateTime) || typeof(TypeToConvert) == typeof(DateTime?))
- {
- success = element.TryGetDateTime(out DateTime value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(DateTimeOffset) || typeof(TypeToConvert) == typeof(DateTimeOffset?))
- {
- success = element.TryGetDateTimeOffset(out DateTimeOffset value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(Guid) || typeof(TypeToConvert) == typeof(Guid?))
- {
- success = element.TryGetGuid(out Guid value);
- result = (TypeToConvert)(object)value;
- return success;
- }
-
- if (typeof(TypeToConvert) == typeof(char) || typeof(TypeToConvert) == typeof(char?))
- {
- string? str = element.GetString();
- Debug.Assert(str != null);
- if (str.Length == 1)
- {
- result = (TypeToConvert)(object)str[0];
- return true;
- }
- }
- break;
-
- case JsonValueKind.True:
- case JsonValueKind.False:
- if (typeof(TypeToConvert) == typeof(bool) || typeof(TypeToConvert) == typeof(bool?))
- {
- result = (TypeToConvert)(object)element.GetBoolean();
- return true;
- }
- break;
- }
-
- result = default!;
- return false;
+ TypeCode.Boolean => JsonValueKind.Undefined, // Can vary dependending on value.
+ TypeCode.SByte => JsonValueKind.Number,
+ TypeCode.Byte => JsonValueKind.Number,
+ TypeCode.Int16 => JsonValueKind.Number,
+ TypeCode.UInt16 => JsonValueKind.Number,
+ TypeCode.Int32 => JsonValueKind.Number,
+ TypeCode.UInt32 => JsonValueKind.Number,
+ TypeCode.Int64 => JsonValueKind.Number,
+ TypeCode.UInt64 => JsonValueKind.Number,
+ TypeCode.Single => JsonValueKind.Number,
+ TypeCode.Double => JsonValueKind.Number,
+ TypeCode.Decimal => JsonValueKind.Number,
+ TypeCode.String => JsonValueKind.String,
+ TypeCode.Char => JsonValueKind.String,
+ _ => null,
+ };
}
[ExcludeFromCodeCoverage] // Justification = "Design-time"
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfTCustomized.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfTCustomized.cs
index 916f7e9bcfc29a..589a8ec8e5556f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfTCustomized.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfTCustomized.cs
@@ -7,18 +7,25 @@
namespace System.Text.Json.Nodes
{
///
- /// A JsonValue encapsulating arbitrary types using custom JsonTypeInfo metadata.
+ /// A JsonValue that encapsulates arbitrary .NET type configurations.
+ /// Paradoxically, instances of this type can be of any JsonValueKind
+ /// (including objects and arrays) and introspecting these values is
+ /// generally slower compared to the other JsonValue implementations.
///
internal sealed class JsonValueCustomized : JsonValue
{
private readonly JsonTypeInfo _jsonTypeInfo;
+ private JsonValueKind? _valueKind;
- public JsonValueCustomized(TValue value, JsonTypeInfo jsonTypeInfo, JsonNodeOptions? options = null) : base(value, options)
+ public JsonValueCustomized(TValue value, JsonTypeInfo jsonTypeInfo, JsonNodeOptions? options = null): base(value, options)
{
Debug.Assert(jsonTypeInfo.IsConfigured);
_jsonTypeInfo = jsonTypeInfo;
}
+ private protected override JsonValueKind GetValueKindCore() => _valueKind ??= ComputeValueKind();
+ internal override JsonNode DeepCloneCore() => JsonSerializer.SerializeToNode(Value, _jsonTypeInfo)!;
+
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
{
if (writer is null)
@@ -37,9 +44,25 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio
jsonTypeInfo.Serialize(writer, Value);
}
- internal override JsonNode DeepCloneCore()
+ ///
+ /// Computes the JsonValueKind of the value by serializing it and reading the resultant JSON.
+ ///
+ private JsonValueKind ComputeValueKind()
{
- return JsonSerializer.SerializeToNode(Value, _jsonTypeInfo)!;
+ Utf8JsonWriter writer = Utf8JsonWriterCache.RentWriterAndBuffer(options: default, JsonSerializerOptions.BufferSizeDefault, out PooledByteBufferWriter output);
+ try
+ {
+ WriteTo(writer);
+ writer.Flush();
+ Utf8JsonReader reader = new(output.WrittenMemory.Span);
+ bool success = reader.Read();
+ Debug.Assert(success);
+ return JsonReaderHelper.ToValueKind(reader.TokenType);
+ }
+ finally
+ {
+ Utf8JsonWriterCache.ReturnWriterAndBuffer(writer, output);
+ }
}
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfTPrimitive.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfTPrimitive.cs
index 87be927a29222f..fce1d5fbf04cf4 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfTPrimitive.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfTPrimitive.cs
@@ -1,9 +1,9 @@
// 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.Text.Json.Serialization;
-using System.Text.Json.Serialization.Metadata;
namespace System.Text.Json.Nodes
{
@@ -12,15 +12,31 @@ namespace System.Text.Json.Nodes
///
internal sealed class JsonValuePrimitive : JsonValue
{
- // Default value used when calling into the converter.
- private static readonly JsonSerializerOptions s_defaultOptions = new();
-
private readonly JsonConverter _converter;
+ private readonly JsonValueKind _valueKind;
- public JsonValuePrimitive(TValue value, JsonConverter converter, JsonNodeOptions? options = null) : base(value, options)
+ public JsonValuePrimitive(TValue value, JsonConverter converter, JsonNodeOptions? options) : base(value, options)
{
+ Debug.Assert(TypeIsSupportedPrimitive, $"The type {typeof(TValue)} is not a supported primitive.");
Debug.Assert(converter is { IsInternalConverter: true, ConverterStrategy: ConverterStrategy.Value });
+
_converter = converter;
+ _valueKind = DetermineValueKind(value);
+ }
+
+ private protected override JsonValueKind GetValueKindCore() => _valueKind;
+ internal override JsonNode DeepCloneCore() => new JsonValuePrimitive(Value, _converter, Options);
+
+ internal override bool DeepEqualsCore(JsonNode otherNode)
+ {
+ if (otherNode is JsonValue otherValue && otherValue.TryGetValue(out TValue? v))
+ {
+ // Because TValue is equatable and otherNode returns a matching
+ // type we can short circuit the comparison in this case.
+ return EqualityComparer.Default.Equals(Value, v);
+ }
+
+ return base.DeepEqualsCore(otherNode);
}
public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
@@ -42,14 +58,5 @@ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? optio
converter.Write(writer, Value, options);
}
}
-
- internal override JsonNode DeepCloneCore()
- {
- // Primitive JsonValue's are generally speaking immutable so we don't need to do much here.
- // For the case of JsonElement clone the instance since it could be backed by pooled buffers.
- return Value is JsonElement element
- ? new JsonValuePrimitive(element.Clone(), JsonMetadataServices.JsonElementConverter, Options)
- : new JsonValuePrimitive(Value, _converter, Options);
- }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs
index 3728ab26ad6f36..af2e3a785728eb 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs
@@ -73,7 +73,7 @@ public override void Write(Utf8JsonWriter writer, JsonNode? value, JsonSerialize
node = new JsonArray(element, options);
break;
default:
- node = new JsonValuePrimitive(element, JsonMetadataServices.JsonElementConverter, options);
+ node = JsonValue.CreateFromElement(ref element, options);
break;
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs
index 51253ddd70c82a..97dbea8bbf7a9e 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs
@@ -28,8 +28,7 @@ public override void Write(Utf8JsonWriter writer, JsonValue? value, JsonSerializ
}
JsonElement element = JsonElement.ParseValue(ref reader);
- JsonValue value = new JsonValuePrimitive(element, JsonMetadataServices.JsonElementConverter, options.GetNodeOptions());
- return value;
+ return JsonValue.CreateFromElement(ref element, options.GetNodeOptions());
}
internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverter.cs
index d5803e65b2cc8e..9aed8af362e9df 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverter.cs
@@ -20,6 +20,7 @@ internal sealed class NullableConverter : JsonConverter where T : struct
public NullableConverter(JsonConverter elementConverter)
{
_elementConverter = elementConverter;
+ IsInternalConverter = elementConverter.IsInternalConverter;
IsInternalConverterForNumberType = elementConverter.IsInternalConverterForNumberType;
ConverterStrategy = elementConverter.ConverterStrategy;
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleMetadata.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleMetadata.cs
index f67431c36d56a1..91b516058e1579 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleMetadata.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleMetadata.cs
@@ -496,15 +496,7 @@ static string ReadAsStringMetadataValue(JsonNode? jsonNode)
return value;
}
- JsonValueKind metadataValueKind = jsonNode switch
- {
- null => JsonValueKind.Null,
- JsonObject => JsonValueKind.Object,
- JsonArray => JsonValueKind.Array,
- JsonValue element => element.Value.ValueKind,
- _ => JsonValueKind.Undefined,
- };
-
+ JsonValueKind metadataValueKind = jsonNode?.GetValueKind() ?? JsonValueKind.Null;
Debug.Assert(metadataValueKind != JsonValueKind.Undefined);
ThrowHelper.ThrowJsonException_MetadataValueWasNotString(metadataValueKind);
return null!;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
index 29f7dcdcca3235..f337adcb567b0c 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
@@ -505,6 +505,7 @@ public JsonNumberHandling? NumberHandling
}
}
+ internal JsonNumberHandling EffectiveNumberHandling => _numberHandling ?? Options.NumberHandling;
private JsonNumberHandling? _numberHandling;
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Node.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Node.cs
index 9fe72311c7cf5b..025a4215ba4727 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Node.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Node.cs
@@ -70,5 +70,17 @@ public static NotSupportedException GetNotSupportedException_CollectionIsReadOnl
{
return new NotSupportedException(SR.CollectionIsReadOnly);
}
+
+ [DoesNotReturn]
+ public static void ThrowInvalidOperationException_NodeUnableToConvert(Type sourceType, Type destinationType)
+ {
+ throw new InvalidOperationException(SR.Format(SR.NodeUnableToConvert, sourceType, destinationType));
+ }
+
+ [DoesNotReturn]
+ public static void ThrowInvalidOperationException_NodeUnableToConvertElement(JsonValueKind valueKind, Type destinationType)
+ {
+ throw new InvalidOperationException(SR.Format(SR.NodeUnableToConvertElement, valueKind, destinationType));
+ }
}
}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs
index ad0bddbd3fd1c5..94505ff194149b 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonValueTests.cs
@@ -1,9 +1,11 @@
// 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.IO;
+using System.Reflection;
using System.Text.Json.Serialization;
-using System.Xml.Linq;
+using System.Text.Json.Serialization.Metadata;
using Xunit;
namespace System.Text.Json.Nodes.Tests
@@ -417,6 +419,20 @@ public static void GetValueKind()
}
}
+ [Theory]
+ [InlineData(JsonNumberHandling.Strict, JsonValueKind.Number)]
+ [InlineData(JsonNumberHandling.AllowReadingFromString, JsonValueKind.Number)]
+ [InlineData(JsonNumberHandling.AllowNamedFloatingPointLiterals, JsonValueKind.Number)]
+ [InlineData(JsonNumberHandling.WriteAsString, JsonValueKind.String)]
+ [InlineData(JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowNamedFloatingPointLiterals, JsonValueKind.String)]
+ public static void GetValueKind_NumberHandling(JsonNumberHandling numberHandling, JsonValueKind expectedKind)
+ {
+ JsonSerializerOptions options = new(JsonSerializerOptions.Default) { NumberHandling = numberHandling };
+ JsonTypeInfo typeInfo = (JsonTypeInfo)options.GetTypeInfo(typeof(int));
+ JsonValue value = JsonValue.Create(42, typeInfo);
+ Assert.Equal(expectedKind, value.GetValueKind());
+ }
+
[Fact]
public static void DeepEquals_EscapedString()
{
@@ -430,5 +446,87 @@ private class Student
public int Id { get; set; }
public string? Name { get; set; }
}
+
+ [Theory]
+ [MemberData(nameof(GetPrimitiveTypes))]
+ public static void PrimitiveTypes_ReturnExpectedTypeKind(T value, JsonValueKind expectedKind)
+ {
+ JsonNode node = JsonValue.Create(value);
+ Assert.Equal(expectedKind, node.GetValueKind());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetPrimitiveTypes))]
+ public static void PrimitiveTypes_EqualThemselves(T value, JsonValueKind _)
+ {
+ JsonNode node = JsonValue.Create(value);
+ Assert.True(JsonNode.DeepEquals(node, node));
+ }
+
+ [Theory]
+ [MemberData(nameof(GetPrimitiveTypes))]
+ public static void PrimitiveTypes_EqualClonedValue(T value, JsonValueKind _)
+ {
+ JsonNode node = JsonValue.Create(value);
+ JsonNode clone = node.DeepClone();
+
+ Assert.True(JsonNode.DeepEquals(clone, clone));
+ Assert.True(JsonNode.DeepEquals(node, clone));
+ Assert.True(JsonNode.DeepEquals(clone, node));
+ }
+
+ [Theory]
+ [MemberData(nameof(GetPrimitiveTypes))]
+ public static void PrimitiveTypes_EqualDeserializedValue(T value, JsonValueKind _)
+ {
+ JsonNode node = JsonValue.Create(value);
+ JsonNode clone = JsonSerializer.Deserialize(node.ToJsonString());
+
+ Assert.True(JsonNode.DeepEquals(clone, clone));
+ Assert.True(JsonNode.DeepEquals(node, clone));
+ Assert.True(JsonNode.DeepEquals(clone, node));
+ }
+
+ public static IEnumerable