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 aee388056c8cc..9f88d70f9860b 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 5a9b20ad023b8..b5d841610ef41 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,13 @@ internal JsonTokenType GetJsonTokenType(int index)
return _parsedData.GetJsonTokenType(index);
}
+ internal bool GetHasComplexChildren(int index)
+ {
+ CheckNotDisposed();
+ DbRow row = _parsedData.Get(index);
+ return row.HasComplexChildren;
+ }
+
internal int GetArrayLength(int index)
{
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 1ca7fff9f7e33..f64689b92c35c 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
@@ -1194,6 +1194,28 @@ internal string GetPropertyRawText()
return _parent.GetPropertyRawValueAsString(_idx);
}
+ internal bool ValueIsEscaped
+ {
+ // TODO make public https://github.com/dotnet/runtime/issues/77666
+ get
+ {
+ Debug.Assert(ValueKind is JsonValueKind.String);
+ CheckValidInstance();
+ return _parent.GetHasComplexChildren(_idx);
+ }
+ }
+
+ internal ReadOnlySpan ValueSpan
+ {
+ // TODO make public https://github.com/dotnet/runtime/issues/77666
+ get
+ {
+ Debug.Assert(ValueKind is JsonValueKind.String);
+ CheckValidInstance();
+ return _parent.GetRawValue(_idx, includeQuotes: false).Span;
+ }
+ }
+
///
/// Compares to the string value of this element.
///
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 70b80505b7a7d..42a3f7f362db3 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,6 +14,9 @@ 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;
@@ -375,7 +378,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/JsonValue.CreateOverloads.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValue.CreateOverloads.cs
index 824869192765f..439274348f4d3 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 5aeb330059056..4fe3ec1a93700 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
@@ -37,20 +37,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,21 +74,22 @@ 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));
}
jsonTypeInfo.EnsureConfigured();
- return new JsonValueCustomized(value, jsonTypeInfo, options);
+
+ if (value is JsonElement element && jsonTypeInfo.EffectiveConverter.IsInternalConverter)
+ {
+ return CreateFromElement(ref element, options);
+ }
+
+ return CreateFromTypeInfo(value, jsonTypeInfo, options);
}
- internal override void GetPath(ref ValueStringBuilder path, JsonNode? child)
+ internal sealed override void GetPath(ref ValueStringBuilder path, JsonNode? child)
{
Debug.Assert(child == null);
@@ -114,13 +113,35 @@ internal override void GetPath(ref ValueStringBuilder path, JsonNode? child)
/// if the value can be successfully obtained; otherwise, .
public abstract bool TryGetValue([NotNullWhen(true)] out T? value);
- private static void VerifyJsonElementIsNotArrayOrObject(ref JsonElement element)
+ internal static JsonValue CreateFromTypeInfo(T value, JsonTypeInfo jsonTypeInfo, JsonNodeOptions? options = null)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
+ Debug.Assert(value != null);
+
+ if (jsonTypeInfo.EffectiveConverter.IsInternalConverter && JsonValue.TypeIsSupportedPrimitive)
+ {
+ // 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);
+ }
+
+ 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 0000000000000..cce6006048538
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfElement.cs
@@ -0,0 +1,232 @@
+// 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 = null) : base(value, options)
+ {
+ Debug.Assert(value.ValueKind is JsonValueKind.False or JsonValueKind.True or JsonValueKind.Number or JsonValueKind.String);
+ }
+
+ private protected override JsonValueKind GetValueKindCore() => Value.ValueKind;
+ internal override JsonNode DeepCloneCore() => new JsonValueOfElement(Value.Clone(), Options);
+
+ internal override bool DeepEqualsCore(JsonNode? otherNode)
+ {
+ if (otherNode is JsonValueOfElement { Value: JsonElement otherElement })
+ {
+ JsonElement thisElement = Value;
+ if (thisElement.ValueKind != otherElement.ValueKind)
+ {
+ return false;
+ }
+
+ switch (thisElement.ValueKind)
+ {
+ case JsonValueKind.Null:
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ return true;
+
+ case JsonValueKind.String:
+ if (otherElement.ValueIsEscaped)
+ {
+ if (thisElement.ValueIsEscaped)
+ {
+ // Both values contain escaping, force an allocation to unescape the RHS.
+ return thisElement.ValueEquals(otherElement.GetString());
+ }
+
+ // Swap values so that unescaping is handled by the LHS.
+ (thisElement, otherElement) = (otherElement, thisElement);
+ }
+
+ return thisElement.ValueEquals(otherElement.ValueSpan);
+
+ case JsonValueKind.Number:
+ return thisElement.GetRawValue().Span.SequenceEqual(otherElement.GetRawValue().Span);
+ default:
+ Debug.Fail("Object and Array JsonElements cannot be contained in JsonValue.");
+ break;
+ }
+ }
+
+ 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 8067c321e0db4..4e4b126ea3458 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
@@ -16,12 +16,7 @@ protected JsonValue(TValue value, JsonNodeOptions? options = null) : base(option
{
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,57 +51,20 @@ public override bool TryGetValue([NotNullWhen(true)] out T value)
return false;
}
- private protected sealed override JsonValueKind GetValueKindCore()
- {
- if (Value is JsonElement element)
- {
- return element.ValueKind;
- }
-
- 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
- {
- Utf8JsonWriterCache.ReturnWriterAndBuffer(writer, output);
- }
- }
-
- internal sealed override bool DeepEqualsCore(JsonNode? otherNode)
+ internal override bool DeepEqualsCore(JsonNode? otherNode)
{
if (otherNode is null)
{
return false;
}
- if (Value is JsonElement thisElement && otherNode is JsonValue { Value: JsonElement otherElement })
+ if (GetValueKind() != otherNode.GetValueKind())
{
- 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;
- }
+ return false;
}
+ // Fall back to slow path equality comparison:
+ // Serialize both nodes and compare the resulting JSON.
using PooledByteBufferWriter thisOutput = WriteToPooledBuffer(this);
using PooledByteBufferWriter otherOutput = WriteToPooledBuffer(otherNode);
return thisOutput.WrittenMemory.Span.SequenceEqual(otherOutput.WrittenMemory.Span);
@@ -129,260 +78,62 @@ static PooledByteBufferWriter WriteToPooledBuffer(
var bufferWriter = new PooledByteBufferWriter(bufferSize);
using var writer = new Utf8JsonWriter(bufferWriter, writerOptions);
node.WriteTo(writer, options);
+ writer.Flush();
return bufferWriter;
}
}
- internal TypeToConvert ConvertJsonElement()
+ ///
+ /// 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)
{
- JsonElement element = (JsonElement)(object)Value!;
+ Debug.Assert(s_valueKind is not null, "Should only be invoked for types that are supported primitives.");
- switch (element.ValueKind)
+ if (value is bool boolean)
{
- 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;
+ // Boolean requires special handling since kind varies by value.
+ return boolean ? JsonValueKind.True : JsonValueKind.False;
}
- throw new InvalidOperationException(SR.Format(SR.NodeUnableToConvertElement,
- element.ValueKind,
- typeof(TypeToConvert)));
+ return s_valueKind.Value;
}
- internal bool TryConvertJsonElement([NotNullWhen(true)] out TypeToConvert result)
+ ///
+ /// Precomputes the JsonValueKind for a given built-in type where possible.
+ ///
+ private static JsonValueKind? DetermineValueKindForType(Type type)
{
- bool success;
-
- JsonElement element = (JsonElement)(object)Value!;
-
- switch (element.ValueKind)
+ if (type == typeof(Guid) || type == typeof(DateTimeOffset))
{
- 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;
+ return JsonValueKind.String;
}
- result = default!;
- return false;
+ return Type.GetTypeCode(type) switch
+ {
+ 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.DateTime => 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 916f7e9bcfc29..589a8ec8e5556 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 87be927a29222..fd85056c1f6e6 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,32 @@ 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)
{
+ Debug.Assert(typeof(IEquatable).IsAssignableFrom(typeof(TValue)), $"{typeof(TValue)} is not equatable.");
+ 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 +59,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 3728ab26ad6f3..af2e3a785728e 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 51253ddd70c82..97dbea8bbf7a9 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/ThrowHelper.Node.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Node.cs
index 9fe72311c7cf5..025a4215ba472 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));
+ }
}
}