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 bf54c84e5433a9..e8106e1f215008 100644
--- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj
+++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
@@ -88,6 +88,7 @@ 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 ca840028bdf975..bf0d5170468773 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
@@ -694,29 +694,7 @@ internal bool TryGetValue(int index, out Guid value)
ReadOnlySpan data = _utf8Json.Span;
ReadOnlySpan segment = data.Slice(row.Location, row.SizeOrLength);
- if (segment.Length > JsonConstants.MaximumEscapedGuidLength)
- {
- value = default;
- return false;
- }
-
- // Segment needs to be unescaped
- if (row.HasComplexChildren)
- {
- return JsonReaderHelper.TryGetEscapedGuid(segment, out value);
- }
-
- Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1);
-
- if (segment.Length == JsonConstants.MaximumFormatGuidLength
- && Utf8Parser.TryParse(segment, out Guid tmp, out _, 'D'))
- {
- value = tmp;
- return true;
- }
-
- value = default;
- return false;
+ return JsonReaderHelper.TryGetValue(segment, row.HasComplexChildren, out value);
}
internal string GetRawValueAsString(int index)
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfJsonPrimitive.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfJsonPrimitive.cs
new file mode 100644
index 00000000000000..379eafb8e13d90
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonValueOfJsonPrimitive.cs
@@ -0,0 +1,318 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Buffers.Text;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Encodings.Web;
+
+namespace System.Text.Json.Nodes
+{
+ internal static class JsonValueOfJsonPrimitive
+ {
+ internal static JsonValue CreatePrimitiveValue(ref Utf8JsonReader reader, JsonNodeOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.False:
+ case JsonTokenType.True:
+ return new JsonValueOfJsonBool(reader.GetBoolean(), options);
+ case JsonTokenType.String:
+ byte[] buffer = new byte[reader.ValueLength];
+ ReadOnlyMemory utf8String = buffer.AsMemory(0, reader.CopyString(buffer));
+ return new JsonValueOfJsonString(utf8String, options);
+ case JsonTokenType.Number:
+ byte[] numberValue = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan.ToArray();
+ return new JsonValueOfJsonNumber(numberValue, options);
+ default:
+ Debug.Fail("Only primitives allowed.");
+ ThrowHelper.ThrowJsonException();
+ return null!; // Unreachable, but required for compilation.
+ }
+ }
+
+ private sealed class JsonValueOfJsonString : JsonValue
+ {
+ private readonly ReadOnlyMemory _value;
+
+ internal JsonValueOfJsonString(ReadOnlyMemory utf8String, JsonNodeOptions? options)
+ : base(options)
+ {
+ _value = utf8String;
+ }
+
+ internal override JsonNode DeepCloneCore() => new JsonValueOfJsonString(_value, Options);
+ private protected override JsonValueKind GetValueKindCore() => JsonValueKind.String;
+
+ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
+ {
+ ArgumentNullException.ThrowIfNull(writer);
+
+ writer.WriteStringValue(_value.Span);
+ }
+
+ public override T GetValue()
+ {
+ if (!TryGetValue(out T? value))
+ {
+ ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(JsonValueKind.String, typeof(T));
+ }
+
+ return value;
+ }
+
+ public override bool TryGetValue([NotNullWhen(true)] out T? value)
+ where T : default
+ {
+ if (typeof(T) == typeof(JsonElement))
+ {
+ value = (T)(object)JsonWriterHelper.WriteString(_value.Span, static serialized => JsonElement.Parse(serialized));
+ return true;
+ }
+
+ if (typeof(T) == typeof(string))
+ {
+ string? result = JsonReaderHelper.TranscodeHelper(_value.Span);
+
+ Debug.Assert(result != null);
+ value = (T)(object)result;
+ return true;
+ }
+
+ bool success;
+
+ if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?))
+ {
+ success = JsonReaderHelper.TryGetValue(_value.Span, isEscaped: false, out DateTime result);
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(DateTimeOffset) || typeof(T) == typeof(DateTimeOffset?))
+ {
+ success = JsonReaderHelper.TryGetValue(_value.Span, isEscaped: false, out DateTimeOffset result);
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?))
+ {
+ success = JsonReaderHelper.TryGetValue(_value.Span, isEscaped: false, out Guid result);
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(char) || typeof(T) == typeof(char?))
+ {
+ string? result = JsonReaderHelper.TranscodeHelper(_value.Span);
+
+ Debug.Assert(result != null);
+ if (result.Length == 1)
+ {
+ value = (T)(object)result[0];
+ return true;
+ }
+ }
+
+ value = default!;
+ return false;
+ }
+ }
+
+ private sealed class JsonValueOfJsonBool : JsonValue
+ {
+ private readonly bool _value;
+
+ private JsonValueKind ValueKind => _value ? JsonValueKind.True : JsonValueKind.False;
+
+ internal JsonValueOfJsonBool(bool value, JsonNodeOptions? options)
+ : base(options)
+ {
+ _value = value;
+ }
+
+ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null) => writer.WriteBooleanValue(_value);
+ internal override JsonNode DeepCloneCore() => new JsonValueOfJsonBool(_value, Options);
+ private protected override JsonValueKind GetValueKindCore() => ValueKind;
+
+ public override T GetValue()
+ {
+ if (!TryGetValue(out T? value))
+ {
+ ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(_value ? JsonValueKind.True : JsonValueKind.False, typeof(T));
+ }
+
+ return value;
+ }
+
+ public override bool TryGetValue([NotNullWhen(true)] out T? value)
+ where T : default
+ {
+ if (typeof(T) == typeof(JsonElement))
+ {
+ value = (T)(object)JsonElement.Parse(_value ? JsonConstants.TrueValue : JsonConstants.FalseValue);
+ return true;
+ }
+
+ if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?))
+ {
+ value = (T)(object)_value;
+ return true;
+ }
+
+ value = default!;
+ return false;
+ }
+ }
+
+ private sealed class JsonValueOfJsonNumber : JsonValue
+ {
+ // This can be optimized to store the decimal point position and the exponent so that
+ // conversion to different numeric types can be done without parsing the string again.
+ // Utf8Parser uses an internal ref struct, Number.NumberBuffer, which is really the
+ // same functionality that we would want here.
+ private readonly byte[] _value;
+
+ internal JsonValueOfJsonNumber(byte[] number, JsonNodeOptions? options)
+ : base(options)
+ {
+ _value = number;
+ }
+
+ internal override JsonNode DeepCloneCore() => new JsonValueOfJsonNumber(_value, Options);
+ private protected override JsonValueKind GetValueKindCore() => JsonValueKind.Number;
+
+ public override T GetValue()
+ {
+ if (!TryGetValue(out T? value))
+ {
+ ThrowHelper.ThrowInvalidOperationException_NodeUnableToConvertElement(JsonValueKind.Number, typeof(T));
+ }
+
+ return value;
+ }
+
+ public override bool TryGetValue([NotNullWhen(true)] out T? value)
+ where T : default
+ {
+ if (typeof(T) == typeof(JsonElement))
+ {
+ value = (T)(object)JsonElement.Parse(_value);
+ return true;
+ }
+
+ bool success;
+
+ if (typeof(T) == typeof(int) || typeof(T) == typeof(int?))
+ {
+ success = Utf8Parser.TryParse(_value, out int result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(long) || typeof(T) == typeof(long?))
+ {
+ success = Utf8Parser.TryParse(_value, out long result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(double) || typeof(T) == typeof(double?))
+ {
+ success = Utf8Parser.TryParse(_value, out double result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(short) || typeof(T) == typeof(short?))
+ {
+ success = Utf8Parser.TryParse(_value, out short result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?))
+ {
+ success = Utf8Parser.TryParse(_value, out decimal result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(byte) || typeof(T) == typeof(byte?))
+ {
+ success = Utf8Parser.TryParse(_value, out byte result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(float) || typeof(T) == typeof(float?))
+ {
+ success = Utf8Parser.TryParse(_value, out float result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(uint) || typeof(T) == typeof(uint?))
+ {
+ success = Utf8Parser.TryParse(_value, out uint result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(ushort) || typeof(T) == typeof(ushort?))
+ {
+ success = Utf8Parser.TryParse(_value, out ushort result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(ulong) || typeof(T) == typeof(ulong?))
+ {
+ success = Utf8Parser.TryParse(_value, out ulong result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(sbyte?))
+ {
+ success = Utf8Parser.TryParse(_value, out sbyte result, out int consumed) &&
+ consumed == _value.Length;
+
+ value = (T)(object)result;
+ return success;
+ }
+
+ value = default!;
+ return false;
+ }
+
+ public override void WriteTo(Utf8JsonWriter writer, JsonSerializerOptions? options = null)
+ {
+ ArgumentNullException.ThrowIfNull(writer);
+
+ writer.WriteNumberValue(_value);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs
index 3c748e8fbb988b..bc27b3b0203f89 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs
@@ -175,6 +175,33 @@ public static bool TryGetEscapedDateTimeOffset(ReadOnlySpan source, out Da
return false;
}
+ public static bool TryGetValue(ReadOnlySpan segment, bool isEscaped, out Guid value)
+ {
+ if (segment.Length > JsonConstants.MaximumEscapedGuidLength)
+ {
+ value = default;
+ return false;
+ }
+
+ // Segment needs to be unescaped
+ if (isEscaped)
+ {
+ return TryGetEscapedGuid(segment, out value);
+ }
+
+ Debug.Assert(segment.IndexOf(JsonConstants.BackSlash) == -1);
+
+ if (segment.Length == JsonConstants.MaximumFormatGuidLength
+ && Utf8Parser.TryParse(segment, out Guid tmp, out _, 'D'))
+ {
+ value = tmp;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
public static bool TryGetEscapedGuid(ReadOnlySpan source, out Guid value)
{
Debug.Assert(source.Length <= JsonConstants.MaximumEscapedGuidLength);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs
index 5d59810e106b4d..8b07f85451bf11 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs
@@ -1360,22 +1360,7 @@ internal bool TryGetGuidCore(out Guid value)
span = ValueSpan;
}
- if (ValueIsEscaped)
- {
- return JsonReaderHelper.TryGetEscapedGuid(span, out value);
- }
-
- Debug.Assert(span.IndexOf(JsonConstants.BackSlash) == -1);
-
- if (span.Length == JsonConstants.MaximumFormatGuidLength
- && Utf8Parser.TryParse(span, out Guid tmp, out _, 'D'))
- {
- value = tmp;
- return true;
- }
-
- value = default;
- return false;
+ return JsonReaderHelper.TryGetValue(span, ValueIsEscaped, out value);
}
}
}
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 bf3e26482a4413..ff7608298514d7 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
@@ -43,23 +43,7 @@ public override void Write(Utf8JsonWriter writer, JsonValue? value, JsonSerializ
internal static JsonValue ReadNonNullPrimitiveValue(ref Utf8JsonReader reader, JsonNodeOptions options)
{
Debug.Assert(reader.TokenType is JsonTokenType.String or JsonTokenType.False or JsonTokenType.True or JsonTokenType.Number);
-
- switch (reader.TokenType)
- {
- case JsonTokenType.String:
- return JsonValue.Create(reader.GetString()!, options);
- case JsonTokenType.False:
- return JsonValue.Create(false, options);
- case JsonTokenType.True:
- return JsonValue.Create(true, options);
- case JsonTokenType.Number:
- // We can't infer CLR type for the number, so we parse it as a JsonElement.
- JsonElement element = JsonElement.ParseValue(ref reader);
- return JsonValue.CreateFromElement(ref element, options)!;
- default:
- Debug.Fail("Unexpected token type for primitive value.");
- throw new JsonException();
- }
+ return JsonValueOfJsonPrimitive.CreatePrimitiveValue(ref reader, options);
}
internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.CreateTrueSchema();
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs
index 8844132888beee..1ab10d31f53b21 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs
@@ -4,6 +4,7 @@
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Text.Encodings.Web;
using System.Text.Unicode;
namespace System.Text.Json
@@ -322,5 +323,73 @@ internal static OperationStatus ToUtf8(ReadOnlySpan source, Span des
}
#endif
}
+
+ internal delegate T WriteCallback(ReadOnlySpan serializedValue);
+
+ internal static T WriteString(ReadOnlySpan utf8Value, WriteCallback writeCallback)
+ {
+ int firstByteToEscape = JsonWriterHelper.NeedsEscaping(utf8Value, JavaScriptEncoder.Default);
+
+ if (firstByteToEscape == -1)
+ {
+ int quotedLength = utf8Value.Length + 2;
+ byte[]? rented = null;
+
+ try
+ {
+ Span quotedValue = quotedLength > JsonConstants.StackallocByteThreshold
+ ? (rented = ArrayPool.Shared.Rent(quotedLength)).AsSpan(0, quotedLength)
+ : stackalloc byte[JsonConstants.StackallocByteThreshold].Slice(0, quotedLength);
+
+ quotedValue[0] = JsonConstants.Quote;
+ utf8Value.CopyTo(quotedValue.Slice(1));
+ quotedValue[quotedValue.Length - 1] = JsonConstants.Quote;
+
+ return writeCallback(quotedValue);
+ }
+ finally
+ {
+ if (rented != null)
+ {
+ ArrayPool.Shared.Return(rented);
+ }
+ }
+ }
+ else
+ {
+ Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
+
+ int length = checked(2 + JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstByteToEscape));
+ byte[]? rented = null;
+
+ try
+ {
+ scoped Span escapedValue;
+
+ if (length > JsonConstants.StackallocByteThreshold)
+ {
+ rented = ArrayPool.Shared.Rent(length);
+ escapedValue = rented;
+ }
+ else
+ {
+ escapedValue = stackalloc byte[JsonConstants.StackallocByteThreshold];
+ }
+
+ escapedValue[0] = JsonConstants.Quote;
+ JsonWriterHelper.EscapeString(utf8Value, escapedValue.Slice(1), firstByteToEscape, JavaScriptEncoder.Default, out int written);
+ escapedValue[1 + written] = JsonConstants.Quote;
+
+ return writeCallback(escapedValue.Slice(0, written + 2));
+ }
+ finally
+ {
+ if (rented != null)
+ {
+ ArrayPool.Shared.Return(rented);
+ }
+ }
+ }
+ }
}
}
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 0719fa58414a59..83a51a3b3a7aaa 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,11 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
+using System.Text.Json.Tests;
using Xunit;
namespace System.Text.Json.Nodes.Tests
@@ -263,6 +265,68 @@ public static void FromElement_ToElement(string json, string expected)
}
}
+ [Theory]
+ [InlineData("\"A\"", "A")]
+ [InlineData("\"AB\"", "AB")]
+ [InlineData("\"A\\u0022\"", "A\"")] // ValueEquals compares unescaped values
+ public static void DeserializePrimitive_ToElement_String(string json, string expected)
+ {
+ DoTest(json, expected);
+
+ // Test long strings
+ string padding = new string('P', 256);
+ DoTest(json[0] + padding + json.Substring(1), padding + expected);
+
+ static void DoTest(string json, string expected)
+ {
+ JsonValue value = JsonSerializer.Deserialize(json);
+ JsonElement element = value.GetValue();
+
+ AssertExtensions.TrueExpression(element.ValueEquals(expected));
+
+ bool success = value.TryGetValue(out element);
+ Assert.True(success);
+ AssertExtensions.TrueExpression(element.ValueEquals(expected));
+ }
+ }
+
+ [Fact]
+ public static void DeserializePrimitive_ToElement_Bool()
+ {
+ // true
+ JsonValue value = JsonSerializer.Deserialize("true");
+
+ JsonElement element = value.GetValue();
+ Assert.Equal(JsonValueKind.True, element.ValueKind);
+
+ bool success = value.TryGetValue(out element);
+ Assert.True(success);
+ Assert.Equal(JsonValueKind.True, element.ValueKind);
+
+ // false
+ value = JsonSerializer.Deserialize("false");
+
+ element = value.GetValue();
+ Assert.Equal(JsonValueKind.False, element.ValueKind);
+
+ success = value.TryGetValue(out element);
+ Assert.True(success);
+ Assert.Equal(JsonValueKind.False, element.ValueKind);
+ }
+
+ [Fact]
+ public static void DeserializePrimitive_ToElement_Number()
+ {
+ JsonValue value = JsonSerializer.Deserialize("42");
+
+ JsonElement element = value.GetValue();
+ Assert.Equal(42, element.GetInt32());
+
+ bool success = value.TryGetValue(out element);
+ Assert.True(success);
+ Assert.Equal(42, element.GetInt32());
+ }
+
[Theory]
[InlineData("42")]
[InlineData("\"AB\"")]
@@ -309,6 +373,26 @@ public static void WriteTo()
Assert.Equal(Json, json);
}
+ [Theory]
+ [InlineData("\"A\"")]
+ [InlineData("\"AB\"")]
+ [InlineData("\"A\\u0022\"")]
+ [InlineData("42")]
+ [InlineData("true")]
+ [InlineData("false")]
+ public static void DeserializePrimitive_WriteTo(string json)
+ {
+ byte[] utf8Json = Encoding.UTF8.GetBytes(json);
+ JsonValue value = JsonSerializer.Deserialize(utf8Json);
+
+ var buffer = new ArrayBufferWriter(json.Length);
+ using Utf8JsonWriter writer = new Utf8JsonWriter(buffer);
+ value.WriteTo(writer);
+ writer.Flush();
+
+ AssertExtensions.SequenceEqual(utf8Json, buffer.WrittenSpan);
+ }
+
[Fact]
public static void DeepCloneNotTrimmable()
{
@@ -547,24 +631,27 @@ private class Student
[Theory]
[MemberData(nameof(GetPrimitiveTypes))]
- public static void PrimitiveTypes_ReturnExpectedTypeKind(T value, JsonValueKind expectedKind)
+ public static void PrimitiveTypes_ReturnExpectedTypeKind(WrappedT wrapped, JsonValueKind expectedKind)
{
+ T value = wrapped.Value;
JsonNode node = JsonValue.Create(value);
Assert.Equal(expectedKind, node.GetValueKind());
}
[Theory]
[MemberData(nameof(GetPrimitiveTypes))]
- public static void PrimitiveTypes_EqualThemselves(T value, JsonValueKind _)
+ public static void PrimitiveTypes_EqualThemselves(WrappedT wrapped, JsonValueKind _)
{
+ T value = wrapped.Value;
JsonNode node = JsonValue.Create(value);
Assert.True(JsonNode.DeepEquals(node, node));
}
[Theory]
[MemberData(nameof(GetPrimitiveTypes))]
- public static void PrimitiveTypes_EqualClonedValue(T value, JsonValueKind _)
+ public static void PrimitiveTypes_EqualClonedValue(WrappedT wrapped, JsonValueKind _)
{
+ T value = wrapped.Value;
JsonNode node = JsonValue.Create(value);
JsonNode clone = node.DeepClone();
@@ -575,8 +662,9 @@ public static void PrimitiveTypes_EqualClonedValue(T value, JsonValueKind _)
[Theory]
[MemberData(nameof(GetPrimitiveTypes))]
- public static void PrimitiveTypes_EqualDeserializedValue(T value, JsonValueKind _)
+ public static void PrimitiveTypes_EqualDeserializedValue(WrappedT wrapped, JsonValueKind _)
{
+ T value = wrapped.Value;
JsonNode node = JsonValue.Create(value);
JsonNode clone = JsonSerializer.Deserialize(node.ToJsonString());
@@ -585,6 +673,100 @@ public static void PrimitiveTypes_EqualDeserializedValue(T value, JsonValueKi
Assert.True(JsonNode.DeepEquals(clone, node));
}
+ [Theory]
+ [MemberData(nameof(GetPrimitiveTypes))]
+ public static void PrimitiveTypes_DeepEquals_DifferentRepresentations(WrappedT wrapped, JsonValueKind _)
+ {
+ T value = wrapped.Value;
+ string json = JsonSerializer.Serialize(value);
+ JsonNode node = JsonSerializer.Deserialize(json);
+ JsonNode other = JsonSerializer.Deserialize($"[{json}]")[0]; // JsonValueOfElement
+
+ Assert.True(JsonNode.DeepEquals(other, other));
+ Assert.True(JsonNode.DeepEquals(node, other));
+ Assert.True(JsonNode.DeepEquals(other, node));
+ }
+
+ [Theory]
+ [MemberData(nameof(GetPrimitiveTypes))]
+ public static void PrimitiveTypes_EqualClonedValue_DeserializedValue(WrappedT wrapped, JsonValueKind _)
+ {
+ T value = wrapped.Value;
+ string json = JsonSerializer.Serialize(value);
+ JsonNode node = JsonSerializer.Deserialize(json);
+ JsonNode clone = node.DeepClone();
+
+ Assert.True(JsonNode.DeepEquals(clone, clone));
+ Assert.True(JsonNode.DeepEquals(node, clone));
+ Assert.True(JsonNode.DeepEquals(clone, node));
+ }
+
+ private static readonly HashSet s_convertibleTypes =
+ [
+ // True/False
+ typeof(bool), typeof(bool?),
+
+ // Number
+ typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
+ typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal),
+
+ typeof(byte?), typeof(sbyte?), typeof(short?), typeof(ushort?), typeof(int?), typeof(uint?),
+ typeof(long?), typeof(ulong?), typeof(float?), typeof(double?), typeof(decimal?),
+
+ // String
+ typeof(char), typeof(char?),
+ typeof(string),
+ typeof(DateTimeOffset), typeof(DateTimeOffset?),
+ typeof(DateTime), typeof(DateTime?),
+ typeof(Guid), typeof(Guid?),
+ ];
+
+ [Theory]
+ [MemberData(nameof(GetPrimitiveTypes))]
+ public static void PrimitiveTypes_Conversion(WrappedT wrapped, JsonValueKind _)
+ {
+ T value = wrapped.Value;
+ string json = JsonSerializer.Serialize(value);
+ bool canGetValue = s_convertibleTypes.Contains(typeof(T));
+
+ JsonValue jsonValue = JsonSerializer.Deserialize(json)!;
+ AssertExtensions.TrueExpression(jsonValue.TryGetValue(out T unused) == canGetValue);
+
+ if (canGetValue)
+ {
+ // Assert no throw
+ jsonValue.GetValue();
+ }
+ else
+ {
+ Assert.Throws(() => jsonValue.GetValue());
+ }
+
+ JsonValue jsonNode = (JsonValue)JsonSerializer.Deserialize(json)!;
+ AssertExtensions.TrueExpression(jsonNode.TryGetValue(out unused) == canGetValue);
+
+ // Ensure the eager evaluation code path also produces the same result
+ jsonNode = (JsonValue)JsonSerializer.Deserialize(json, new JsonSerializerOptions { AllowDuplicateProperties = false })!;
+ AssertExtensions.TrueExpression(jsonNode.TryGetValue(out unused) == canGetValue);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetPrimitiveTypes))]
+ public static void PrimitiveTypes_ReadOnlySequence(WrappedT wrapped, JsonValueKind _)
+ {
+ T value = wrapped.Value;
+
+ byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(value);
+ ReadOnlySequence seq = BufferFactory.Create([jsonBytes.AsMemory(0, 1), jsonBytes.AsMemory(1, jsonBytes.Length - 1)]);
+ Utf8JsonReader reader = new Utf8JsonReader(seq);
+ JsonValue jsonValueFromSequence = JsonSerializer.Deserialize(ref reader)!;
+
+ string jsonString = JsonSerializer.Serialize(value);
+ JsonValue jsonValueFromString = JsonSerializer.Deserialize(jsonString)!;
+
+ AssertExtensions.TrueExpression(JsonNode.DeepEquals(jsonValueFromString, jsonValueFromSequence));
+ }
+
public static IEnumerable