From 9fa6f465c31e32d313541de6a71a018806f23f1d Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 14 Jun 2021 15:23:57 -0700 Subject: [PATCH 01/10] Added TimeSpanConverter. --- .../System.Text.Json/ref/System.Text.Json.cs | 1 + .../src/Resources/Strings.resx | 3 ++ .../src/System.Text.Json.csproj | 1 + .../Converters/Value/TimeSpanConverter.cs | 47 +++++++++++++++++++ .../JsonSerializerOptions.Converters.cs | 3 +- .../JsonMetadataServices.Converters.cs | 6 +++ .../src/System/Text/Json/ThrowHelper.cs | 4 ++ .../Serialization/Value.ReadTests.cs | 32 +++++++++++++ .../Serialization/Value.WriteTests.cs | 15 ++++++ 9 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index ed8880c117fea..d78759d416a18 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -887,6 +887,7 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.JsonConverter SByteConverter { get { throw null; } } public static System.Text.Json.Serialization.JsonConverter SingleConverter { get { throw null; } } public static System.Text.Json.Serialization.JsonConverter StringConverter { get { throw null; } } + public static System.Text.Json.Serialization.JsonConverter TimeSpanConverter { get { throw null; } } [System.CLSCompliantAttribute(false)] public static System.Text.Json.Serialization.JsonConverter UInt16Converter { get { throw null; } } [System.CLSCompliantAttribute(false)] diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 9f73cb0ec2956..c650f6ebad8bb 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -318,6 +318,9 @@ The JSON value is not in a supported DateTimeOffset format. + + The JSON value is not in a supported TimeSpan format. + The JSON value is not in a supported Guid format. 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 e3ba73d6b6c23..10a02012449bb 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -165,6 +165,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs new file mode 100644 index 0000000000000..5c56e7fedcd54 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs @@ -0,0 +1,47 @@ +// 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.Text; +using System.Diagnostics; + +namespace System.Text.Json.Serialization.Converters +{ + internal sealed class TimeSpanConverter : JsonConverter + { + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + { + throw ThrowHelper.GetInvalidOperationException_ExpectedString(reader.TokenType); + } + + var source = reader.GetSpan(); + + bool result = Utf8Parser.TryParse(source, out TimeSpan tmpValue, out int bytesConsumed, 'c'); + + // Note: Utf8Parser.TryParse will return true for invalid input so + // long as it starts with an integer. Example: "2021-06-18" or + // "1$$$$$$$$$$". We need to check bytesConsumed to know if the + // entire source was actually valid. + + if (result && source.Length == bytesConsumed) + { + return tmpValue; + } + + throw ThrowHelper.GetFormatException(); + } + + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) + { + const int MaximumTimeSpanFormatLength = 25; // -ddddddd.hh:mm:ss.fffffff + + Span output = stackalloc byte[MaximumTimeSpanFormatLength]; + + bool result = Utf8Formatter.TryFormat(value, output, out int bytesWritten, 'c'); + Debug.Assert(result); + + writer.WriteStringValue(output.Slice(0, bytesWritten)); + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs index 95de3062e667f..a5270028def60 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs @@ -49,7 +49,7 @@ private void RootBuiltInConverters() private static Dictionary GetDefaultSimpleConverters() { - const int NumberOfSimpleConverters = 23; + const int NumberOfSimpleConverters = 24; var converters = new Dictionary(NumberOfSimpleConverters); // Use a dictionary for simple converters. @@ -72,6 +72,7 @@ private static Dictionary GetDefaultSimpleConverters() Add(JsonMetadataServices.SByteConverter); Add(JsonMetadataServices.SingleConverter); Add(JsonMetadataServices.StringConverter); + Add(JsonMetadataServices.TimeSpanConverter); Add(JsonMetadataServices.UInt16Converter); Add(JsonMetadataServices.UInt32Converter); Add(JsonMetadataServices.UInt64Converter); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs index 0be5273425cdb..f51bc851a0a53 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs @@ -110,6 +110,12 @@ public static partial class JsonMetadataServices public static JsonConverter StringConverter => s_stringConverter ??= new StringConverter(); private static JsonConverter? s_stringConverter; + /// + /// Returns a instance that converts values. + /// + public static JsonConverter TimeSpanConverter => s_timeSpanConverter ??= new TimeSpanConverter(); + private static JsonConverter? s_timeSpanConverter; + /// /// Returns a instance that converts values. /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index 2b6b85878c9ae..96296fa7b2fed 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -636,6 +636,9 @@ public static FormatException GetFormatException(DataType dateType) case DataType.DateTimeOffset: message = SR.FormatDateTimeOffset; break; + case DataType.TimeSpan: + message = SR.FormatTimeSpan; + break; case DataType.Base64String: message = SR.CannotDecodeInvalidBase64; break; @@ -723,6 +726,7 @@ internal enum DataType Boolean, DateTime, DateTimeOffset, + TimeSpan, Base64String, Guid, } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index b4a8d0db76e21..da125afc792bf 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -80,6 +80,7 @@ public static void ReadPrimitivesFail() Assert.Throws(() => JsonSerializer.Deserialize("\"abc\"")); Assert.Throws(() => JsonSerializer.Deserialize("\"abc\"")); + Assert.Throws(() => JsonSerializer.Deserialize("\"abc\"")); Assert.Throws(() => JsonSerializer.Deserialize("\"abc\"")); Assert.Throws(() => JsonSerializer.Deserialize("\"abc\"")); @@ -118,6 +119,7 @@ public static void ReadPrimitivesFail() [InlineData(typeof(sbyte))] [InlineData(typeof(float))] [InlineData(typeof(string))] + [InlineData(typeof(TimeSpan))] [InlineData(typeof(ushort))] [InlineData(typeof(uint))] [InlineData(typeof(ulong))] @@ -303,6 +305,9 @@ public static void ValueFail() Assert.Throws(() => JsonSerializer.Deserialize(unexpectedString)); Assert.Throws(() => JsonSerializer.Deserialize(unexpectedString)); + Assert.Throws(() => JsonSerializer.Deserialize(unexpectedString)); + Assert.Throws(() => JsonSerializer.Deserialize(unexpectedString)); + Assert.Throws(() => JsonSerializer.Deserialize("1")); Assert.Throws(() => JsonSerializer.Deserialize("1")); @@ -442,5 +447,32 @@ private static void DeserializeLongJsonString(int stringLength) string str = JsonSerializer.Deserialize(json); Assert.True(json.AsSpan(1, json.Length - 2).SequenceEqual(str.AsSpan())); } + + [Theory] + [InlineData("23:59:59")] + [InlineData("23:59:59.9")] + [InlineData("23:59:59.9999999")] + [InlineData("9999999.23:59:59.9999999")] + [InlineData("-9999999.23:59:59.9999999")] + public static void TimeSpan_Read_Success(string json) + { + TimeSpan value = JsonSerializer.Deserialize($"\"{json}\""); + + Assert.Equal(TimeSpan.Parse(json), value); + } + + [Theory] + [InlineData("24:00:00")] + [InlineData("00:60:00")] + [InlineData("00:00:60")] + [InlineData("00:00:00.00000009")] + [InlineData("900000000.00:00:00")] + [InlineData("+00:00:00")] + [InlineData("2021-06-18")] + [InlineData("1$")] + public static void TimeSpan_Read_Failure(string json) + { + Assert.Throws(() => JsonSerializer.Deserialize($"\"{json}\"")); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.WriteTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.WriteTests.cs index 210cc6fbde566..d076f67f7537b 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.WriteTests.cs @@ -112,5 +112,20 @@ public static void WritePrimitives() Assert.Equal(@"""1.2.3.4""", JsonSerializer.Serialize(version)); } } + + [Theory] + [InlineData("1:59:59", "01:59:59")] + [InlineData("23:59:59")] + [InlineData("23:59:59.9", "23:59:59.9000000")] + [InlineData("23:59:59.9999999")] + [InlineData("1.23:59:59")] + [InlineData("9999999.23:59:59.9999999")] + [InlineData("-9999999.23:59:59.9999999")] + public static void TimeSpan_Write_Success(string value, string? expectedValue = null) + { + string json = JsonSerializer.Serialize(TimeSpan.Parse(value)); + + Assert.Equal($"\"{expectedValue ?? value}\"", json); + } } } From 03b10a30a79d10b6a4b5a633a58fa10d17c996a9 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 15 Jun 2021 12:48:31 -0700 Subject: [PATCH 02/10] Code review. --- .../Converters/Value/TimeSpanConverter.cs | 9 +++++- .../Serialization/Value.ReadTests.cs | 31 +++++++++++++++++-- .../Serialization/Value.WriteTests.cs | 6 +++- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs index 5c56e7fedcd54..e4f3d4f02179f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs @@ -29,12 +29,19 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso return tmpValue; } + result = Utf8Parser.TryParse(source, out tmpValue, out bytesConsumed, 'g'); + + if (result && source.Length == bytesConsumed) + { + return tmpValue; + } + throw ThrowHelper.GetFormatException(); } public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) { - const int MaximumTimeSpanFormatLength = 25; // -ddddddd.hh:mm:ss.fffffff + const int MaximumTimeSpanFormatLength = 26; // -dddddddd.hh:mm:ss.fffffff Span output = stackalloc byte[MaximumTimeSpanFormatLength]; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index da125afc792bf..629023bd97397 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Runtime.CompilerServices; +using Newtonsoft.Json; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -450,15 +451,37 @@ private static void DeserializeLongJsonString(int stringLength) [Theory] [InlineData("23:59:59")] - [InlineData("23:59:59.9")] + [InlineData("23:59:59.9", "23:59:59.9000000")] [InlineData("23:59:59.9999999")] + [InlineData("1:00:00", "01:00:00")] // 'g' Format + [InlineData("1:00:00:00", "1.00:00:00")] // 'g' Format [InlineData("9999999.23:59:59.9999999")] [InlineData("-9999999.23:59:59.9999999")] - public static void TimeSpan_Read_Success(string json) + [InlineData("10675199.02:48:05.4775807")] // TimeSpan.MaxValue + [InlineData("-10675199.02:48:05.4775808")] // TimeSpan.MinValue + public static void TimeSpan_Read_Success(string json, string? actual = null) { TimeSpan value = JsonSerializer.Deserialize($"\"{json}\""); - Assert.Equal(TimeSpan.Parse(json), value); + Assert.Equal(TimeSpan.Parse(actual ?? json), value); + Assert.Equal(value, JsonConvert.DeserializeObject($"\"{json}\"")); + } + + [Fact] + public static void TimeSpan_Read_KnownDifferences() + { + string value = "24:00:00"; + + // 24:00:00 should be invalid because hours can only be up to 23. + Assert.Throws(() => JsonSerializer.Deserialize($"\"{value}\"")); + + TimeSpan expectedValue = TimeSpan.Parse("24.00:00:00"); + + // TimeSpan.Parse has a quirk where it treats 24:00:00 as 24.00:00:00. + Assert.Equal(expectedValue, TimeSpan.Parse(value)); + + // Newtonsoft uses TimeSpan.Parse so it is subject to the quirk. + Assert.Equal(expectedValue, JsonConvert.DeserializeObject($"\"{value}\"")); } [Theory] @@ -470,6 +493,8 @@ public static void TimeSpan_Read_Success(string json) [InlineData("+00:00:00")] [InlineData("2021-06-18")] [InlineData("1$")] + [InlineData("10675199.02:48:05.4775808")] // TimeSpan.MaxValue + 1 + [InlineData("-10675199.02:48:05.4775809")] // TimeSpan.MinValue - 1 public static void TimeSpan_Read_Failure(string json) { Assert.Throws(() => JsonSerializer.Deserialize($"\"{json}\"")); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.WriteTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.WriteTests.cs index d076f67f7537b..f3e6bd8d3a447 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.WriteTests.cs @@ -121,11 +121,15 @@ public static void WritePrimitives() [InlineData("1.23:59:59")] [InlineData("9999999.23:59:59.9999999")] [InlineData("-9999999.23:59:59.9999999")] + [InlineData("10675199.02:48:05.4775807")] // TimeSpan.MaxValue + [InlineData("-10675199.02:48:05.4775808")] // TimeSpan.MinValue public static void TimeSpan_Write_Success(string value, string? expectedValue = null) { - string json = JsonSerializer.Serialize(TimeSpan.Parse(value)); + TimeSpan ts = TimeSpan.Parse(value); + string json = JsonSerializer.Serialize(ts); Assert.Equal($"\"{expectedValue ?? value}\"", json); + Assert.Equal(json, JsonConvert.SerializeObject(ts)); } } } From 9c5c1f6ab8032cba6209fa4288644e2c5c695ee4 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 15 Jun 2021 13:04:40 -0700 Subject: [PATCH 03/10] Test tweak. --- .../System.Text.Json.Tests/Serialization/Value.ReadTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index 629023bd97397..c6908f9c7f9a4 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -454,7 +454,7 @@ private static void DeserializeLongJsonString(int stringLength) [InlineData("23:59:59.9", "23:59:59.9000000")] [InlineData("23:59:59.9999999")] [InlineData("1:00:00", "01:00:00")] // 'g' Format - [InlineData("1:00:00:00", "1.00:00:00")] // 'g' Format + [InlineData("1:2:00:00", "1.02:00:00")] // 'g' Format [InlineData("9999999.23:59:59.9999999")] [InlineData("-9999999.23:59:59.9999999")] [InlineData("10675199.02:48:05.4775807")] // TimeSpan.MaxValue From bce438f3e8c92960d1d2750c8079840ce9daf2aa Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 15 Jun 2021 13:32:30 -0700 Subject: [PATCH 04/10] Added invalid cases. --- .../Serialization/Value.ReadTests.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index c6908f9c7f9a4..0f35c4a0c1411 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -495,9 +495,17 @@ public static void TimeSpan_Read_KnownDifferences() [InlineData("1$")] [InlineData("10675199.02:48:05.4775808")] // TimeSpan.MaxValue + 1 [InlineData("-10675199.02:48:05.4775809")] // TimeSpan.MinValue - 1 - public static void TimeSpan_Read_Failure(string json) + [InlineData("1234", false)] + [InlineData("{}", false)] + [InlineData("[]", false)] + [InlineData("true", false)] + [InlineData("null", false)] + public static void TimeSpan_Read_Failure(string json, bool addQuotes = true) { - Assert.Throws(() => JsonSerializer.Deserialize($"\"{json}\"")); + if (addQuotes) + json = $"\"{json}\""; + + Assert.Throws(() => JsonSerializer.Deserialize(json)); } } } From 0293935ecbbf646bba68ce29ef57a4ced3f7f9c9 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 21 Jun 2021 10:30:09 -0700 Subject: [PATCH 05/10] Remove the ToArray call in the case of ValueSequence. --- .../Converters/Value/TimeSpanConverter.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs index e4f3d4f02179f..21b6920b3bff1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs @@ -1,6 +1,7 @@ // 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; @@ -8,6 +9,9 @@ namespace System.Text.Json.Serialization.Converters { internal sealed class TimeSpanConverter : JsonConverter { + private const int MinimumTimeSpanFormatLength = 7; // h:mm:ss + private const int MaximumTimeSpanFormatLength = 26; // -dddddddd.hh:mm:ss.fffffff + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.String) @@ -15,7 +19,32 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso throw ThrowHelper.GetInvalidOperationException_ExpectedString(reader.TokenType); } - var source = reader.GetSpan(); + ReadOnlySpan source = stackalloc byte[0]; + + if (reader.HasValueSequence) + { + ReadOnlySequence valueSequence = reader.ValueSequence; + long sequenceLength = valueSequence.Length; + + if (!JsonHelpers.IsInRangeInclusive(sequenceLength, MinimumTimeSpanFormatLength, MaximumTimeSpanFormatLength)) + { + throw ThrowHelper.GetFormatException(); + } + + Span stackSpan = stackalloc byte[(int)sequenceLength]; + + valueSequence.CopyTo(stackSpan); + source = stackSpan; + } + else + { + source = reader.ValueSpan; + + if (!JsonHelpers.IsInRangeInclusive(source.Length, MinimumTimeSpanFormatLength, MaximumTimeSpanFormatLength)) + { + throw ThrowHelper.GetFormatException(); + } + } bool result = Utf8Parser.TryParse(source, out TimeSpan tmpValue, out int bytesConsumed, 'c'); @@ -41,8 +70,6 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) { - const int MaximumTimeSpanFormatLength = 26; // -dddddddd.hh:mm:ss.fffffff - Span output = stackalloc byte[MaximumTimeSpanFormatLength]; bool result = Utf8Formatter.TryFormat(value, output, out int bytesWritten, 'c'); From cf8d104523ca1d00e0e0d44432e7f9a685d04320 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 25 Jun 2021 11:49:20 -0700 Subject: [PATCH 06/10] Support escaped strings in TimeSpanConverter. --- .../Converters/Value/TimeSpanConverter.cs | 22 +++++++++++++++++-- .../Serialization/Value.ReadTests.cs | 2 ++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs index 21b6920b3bff1..62a5c502e8f42 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs @@ -11,6 +11,7 @@ internal sealed class TimeSpanConverter : JsonConverter { private const int MinimumTimeSpanFormatLength = 7; // h:mm:ss private const int MaximumTimeSpanFormatLength = 26; // -dddddddd.hh:mm:ss.fffffff + private const int MaximumEscapedTimeSpanFormatLength = JsonConstants.MaxExpansionFactorWhileEscaping * MaximumTimeSpanFormatLength; public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { @@ -19,6 +20,9 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso throw ThrowHelper.GetInvalidOperationException_ExpectedString(reader.TokenType); } + bool isEscaped = reader._stringHasEscaping; + int maximumLength = isEscaped ? MaximumEscapedTimeSpanFormatLength : MaximumTimeSpanFormatLength; + ReadOnlySpan source = stackalloc byte[0]; if (reader.HasValueSequence) @@ -26,7 +30,7 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso ReadOnlySequence valueSequence = reader.ValueSequence; long sequenceLength = valueSequence.Length; - if (!JsonHelpers.IsInRangeInclusive(sequenceLength, MinimumTimeSpanFormatLength, MaximumTimeSpanFormatLength)) + if (!JsonHelpers.IsInRangeInclusive(sequenceLength, MinimumTimeSpanFormatLength, maximumLength)) { throw ThrowHelper.GetFormatException(); } @@ -40,12 +44,26 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso { source = reader.ValueSpan; - if (!JsonHelpers.IsInRangeInclusive(source.Length, MinimumTimeSpanFormatLength, MaximumTimeSpanFormatLength)) + if (!JsonHelpers.IsInRangeInclusive(source.Length, MinimumTimeSpanFormatLength, maximumLength)) { throw ThrowHelper.GetFormatException(); } } + if (isEscaped) + { + int backslash = source.IndexOf(JsonConstants.BackSlash); + Debug.Assert(backslash != -1); + + Span sourceUnescaped = stackalloc byte[source.Length]; + + JsonReaderHelper.Unescape(source, sourceUnescaped, backslash, out int written); + Debug.Assert(written > 0); + + source = sourceUnescaped.Slice(0, written); + Debug.Assert(!source.IsEmpty); + } + bool result = Utf8Parser.TryParse(source, out TimeSpan tmpValue, out int bytesConsumed, 'c'); // Note: Utf8Parser.TryParse will return true for invalid input so diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index 0f35c4a0c1411..73ae5b2242ecb 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -451,6 +451,7 @@ private static void DeserializeLongJsonString(int stringLength) [Theory] [InlineData("23:59:59")] + [InlineData("\\u0032\\u0033\\u003A\\u0035\\u0039\\u003A\\u0035\\u0039", "23:59:59")] [InlineData("23:59:59.9", "23:59:59.9000000")] [InlineData("23:59:59.9999999")] [InlineData("1:00:00", "01:00:00")] // 'g' Format @@ -486,6 +487,7 @@ public static void TimeSpan_Read_KnownDifferences() [Theory] [InlineData("24:00:00")] + [InlineData("\\u0032\\u0034\\u003A\\u0030\\u0030\\u003A\\u0030\\u0030")] [InlineData("00:60:00")] [InlineData("00:00:60")] [InlineData("00:00:00.00000009")] From 2838c26eb85a47a0ba8581d2977dd522b51b304e Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 28 Jun 2021 11:32:47 -0700 Subject: [PATCH 07/10] Removed 'g' format fallback. --- .../Serialization/Converters/Value/TimeSpanConverter.cs | 7 ------- .../Serialization/Value.ReadTests.cs | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs index 62a5c502e8f42..851f09b67e9e3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs @@ -76,13 +76,6 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso return tmpValue; } - result = Utf8Parser.TryParse(source, out tmpValue, out bytesConsumed, 'g'); - - if (result && source.Length == bytesConsumed) - { - return tmpValue; - } - throw ThrowHelper.GetFormatException(); } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index 73ae5b2242ecb..f233d73fbaebf 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -454,8 +454,7 @@ private static void DeserializeLongJsonString(int stringLength) [InlineData("\\u0032\\u0033\\u003A\\u0035\\u0039\\u003A\\u0035\\u0039", "23:59:59")] [InlineData("23:59:59.9", "23:59:59.9000000")] [InlineData("23:59:59.9999999")] - [InlineData("1:00:00", "01:00:00")] // 'g' Format - [InlineData("1:2:00:00", "1.02:00:00")] // 'g' Format + [InlineData("1:00:00", "01:00:00")] // 'g' Format, allowed by Utf8Parser 'c' parser [InlineData("9999999.23:59:59.9999999")] [InlineData("-9999999.23:59:59.9999999")] [InlineData("10675199.02:48:05.4775807")] // TimeSpan.MaxValue @@ -492,6 +491,7 @@ public static void TimeSpan_Read_KnownDifferences() [InlineData("00:00:60")] [InlineData("00:00:00.00000009")] [InlineData("900000000.00:00:00")] + [InlineData("1:2:00:00")] // 'g' Format, disallowed by Utf8Parser 'c' parser [InlineData("+00:00:00")] [InlineData("2021-06-18")] [InlineData("1$")] From 0248a1529654fe99bb029b2a77728a0dbf5f0c6a Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Mon, 28 Jun 2021 11:52:31 -0700 Subject: [PATCH 08/10] Fixed 'h:mm:ss' being accepted by TimeSpanConverter. --- .../Json/Serialization/Converters/Value/TimeSpanConverter.cs | 2 +- .../System.Text.Json.Tests/Serialization/Value.ReadTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs index 851f09b67e9e3..1f17a02a2bf3a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs @@ -9,7 +9,7 @@ namespace System.Text.Json.Serialization.Converters { internal sealed class TimeSpanConverter : JsonConverter { - private const int MinimumTimeSpanFormatLength = 7; // h:mm:ss + private const int MinimumTimeSpanFormatLength = 8; // hh:mm:ss private const int MaximumTimeSpanFormatLength = 26; // -dddddddd.hh:mm:ss.fffffff private const int MaximumEscapedTimeSpanFormatLength = JsonConstants.MaxExpansionFactorWhileEscaping * MaximumTimeSpanFormatLength; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index f233d73fbaebf..57e5e4c2a98fe 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -454,7 +454,6 @@ private static void DeserializeLongJsonString(int stringLength) [InlineData("\\u0032\\u0033\\u003A\\u0035\\u0039\\u003A\\u0035\\u0039", "23:59:59")] [InlineData("23:59:59.9", "23:59:59.9000000")] [InlineData("23:59:59.9999999")] - [InlineData("1:00:00", "01:00:00")] // 'g' Format, allowed by Utf8Parser 'c' parser [InlineData("9999999.23:59:59.9999999")] [InlineData("-9999999.23:59:59.9999999")] [InlineData("10675199.02:48:05.4775807")] // TimeSpan.MaxValue @@ -491,7 +490,8 @@ public static void TimeSpan_Read_KnownDifferences() [InlineData("00:00:60")] [InlineData("00:00:00.00000009")] [InlineData("900000000.00:00:00")] - [InlineData("1:2:00:00")] // 'g' Format, disallowed by Utf8Parser 'c' parser + [InlineData("1:00:00")] // 'g' Format + [InlineData("1:2:00:00")] // 'g' Format [InlineData("+00:00:00")] [InlineData("2021-06-18")] [InlineData("1$")] From f875e6f862b665d56f2df0ac9705421f6d7ec342 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 6 Jul 2021 16:24:58 -0700 Subject: [PATCH 09/10] Code review. --- .../src/System/Text/Json/JsonHelpers.Date.cs | 6 --- .../Text/Json/Reader/Utf8JsonReader.TryGet.cs | 35 ++++++------- .../Converters/Value/TimeSpanConverter.cs | 19 ++++--- .../JsonDateTimeTestData.cs | 2 +- .../Serialization/Value.ReadTests.cs | 3 ++ .../Utf8JsonReaderTests.TryGet.Date.cs | 51 +++++++++++++++++++ 6 files changed, 86 insertions(+), 30 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Date.cs b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Date.cs index a4dc832223df1..7f05dfafc5173 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Date.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.Date.cs @@ -117,12 +117,6 @@ public static bool IsValidDateTimeOffsetParseLength(int length) return IsInRangeInclusive(length, JsonConstants.MinimumDateTimeParseLength, JsonConstants.MaximumEscapedDateTimeOffsetParseLength); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsValidDateTimeOffsetParseLength(long length) - { - return IsInRangeInclusive(length, JsonConstants.MinimumDateTimeParseLength, JsonConstants.MaximumEscapedDateTimeOffsetParseLength); - } - /// /// Parse the given UTF-8 as extended ISO 8601 format. /// 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 32947d9f090ae..0af169f2e1df7 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 @@ -1073,25 +1073,25 @@ internal bool TryGetDateTimeCore(out DateTime value) { ReadOnlySpan span = stackalloc byte[0]; + int maximumLength = _stringHasEscaping ? JsonConstants.MaximumEscapedDateTimeOffsetParseLength : JsonConstants.MaximumDateTimeOffsetParseLength; + if (HasValueSequence) { long sequenceLength = ValueSequence.Length; - - if (!JsonHelpers.IsValidDateTimeOffsetParseLength(sequenceLength)) + if (!JsonHelpers.IsInRangeInclusive(sequenceLength, JsonConstants.MinimumDateTimeParseLength, maximumLength)) { value = default; return false; } Debug.Assert(sequenceLength <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength); - Span stackSpan = stackalloc byte[(int)sequenceLength]; - + Span stackSpan = stackalloc byte[_stringHasEscaping ? JsonConstants.MaximumEscapedDateTimeOffsetParseLength : JsonConstants.MaximumDateTimeOffsetParseLength]; ValueSequence.CopyTo(stackSpan); - span = stackSpan; + span = stackSpan.Slice(0, (int)sequenceLength); } else { - if (!JsonHelpers.IsValidDateTimeOffsetParseLength(ValueSpan.Length)) + if (!JsonHelpers.IsInRangeInclusive(ValueSpan.Length, JsonConstants.MinimumDateTimeParseLength, maximumLength)) { value = default; return false; @@ -1141,25 +1141,25 @@ internal bool TryGetDateTimeOffsetCore(out DateTimeOffset value) { ReadOnlySpan span = stackalloc byte[0]; + int maximumLength = _stringHasEscaping ? JsonConstants.MaximumEscapedDateTimeOffsetParseLength : JsonConstants.MaximumDateTimeOffsetParseLength; + if (HasValueSequence) { long sequenceLength = ValueSequence.Length; - - if (!JsonHelpers.IsValidDateTimeOffsetParseLength(sequenceLength)) + if (!JsonHelpers.IsInRangeInclusive(sequenceLength, JsonConstants.MinimumDateTimeParseLength, maximumLength)) { value = default; return false; } Debug.Assert(sequenceLength <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength); - Span stackSpan = stackalloc byte[(int)sequenceLength]; - + Span stackSpan = stackalloc byte[_stringHasEscaping ? JsonConstants.MaximumEscapedDateTimeOffsetParseLength : JsonConstants.MaximumDateTimeOffsetParseLength]; ValueSequence.CopyTo(stackSpan); - span = stackSpan; + span = stackSpan.Slice(0, (int)sequenceLength); } else { - if (!JsonHelpers.IsValidDateTimeOffsetParseLength(ValueSpan.Length)) + if (!JsonHelpers.IsInRangeInclusive(ValueSpan.Length, JsonConstants.MinimumDateTimeParseLength, maximumLength)) { value = default; return false; @@ -1210,24 +1210,25 @@ internal bool TryGetGuidCore(out Guid value) { ReadOnlySpan span = stackalloc byte[0]; + int maximumLength = _stringHasEscaping ? JsonConstants.MaximumEscapedGuidLength : JsonConstants.MaximumFormatGuidLength; + if (HasValueSequence) { long sequenceLength = ValueSequence.Length; - if (sequenceLength > JsonConstants.MaximumEscapedGuidLength) + if (sequenceLength > maximumLength) { value = default; return false; } Debug.Assert(sequenceLength <= JsonConstants.MaximumEscapedGuidLength); - Span stackSpan = stackalloc byte[(int)sequenceLength]; - + Span stackSpan = stackalloc byte[_stringHasEscaping ? JsonConstants.MaximumEscapedGuidLength : JsonConstants.MaximumFormatGuidLength]; ValueSequence.CopyTo(stackSpan); - span = stackSpan; + span = stackSpan.Slice(0, (int)sequenceLength); } else { - if (ValueSpan.Length > JsonConstants.MaximumEscapedGuidLength) + if (ValueSpan.Length > maximumLength) { value = default; return false; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs index 1f17a02a2bf3a..3767b7ff6d451 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs @@ -32,13 +32,12 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso if (!JsonHelpers.IsInRangeInclusive(sequenceLength, MinimumTimeSpanFormatLength, maximumLength)) { - throw ThrowHelper.GetFormatException(); + throw ThrowHelper.GetFormatException(DataType.TimeSpan); } - Span stackSpan = stackalloc byte[(int)sequenceLength]; - + Span stackSpan = stackalloc byte[isEscaped ? MaximumEscapedTimeSpanFormatLength : MaximumTimeSpanFormatLength]; valueSequence.CopyTo(stackSpan); - source = stackSpan; + source = stackSpan.Slice(0, (int)sequenceLength); } else { @@ -46,7 +45,7 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso if (!JsonHelpers.IsInRangeInclusive(source.Length, MinimumTimeSpanFormatLength, maximumLength)) { - throw ThrowHelper.GetFormatException(); + throw ThrowHelper.GetFormatException(DataType.TimeSpan); } } @@ -64,6 +63,14 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso Debug.Assert(!source.IsEmpty); } + byte firstChar = source[0]; + if (!JsonHelpers.IsDigit(firstChar) && firstChar != '-') + { + // Note: Utf8Parser.TryParse allows for leading whitespace so we + // need to exclude that case here. + throw ThrowHelper.GetFormatException(DataType.TimeSpan); + } + bool result = Utf8Parser.TryParse(source, out TimeSpan tmpValue, out int bytesConsumed, 'c'); // Note: Utf8Parser.TryParse will return true for invalid input so @@ -76,7 +83,7 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso return tmpValue; } - throw ThrowHelper.GetFormatException(); + throw ThrowHelper.GetFormatException(DataType.TimeSpan); } public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonDateTimeTestData.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonDateTimeTestData.cs index 799f2abd0b407..8d4efb5e3cb2a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonDateTimeTestData.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonDateTimeTestData.cs @@ -189,7 +189,6 @@ public static IEnumerable InvalidISO8601Tests() yield return new object[] { "\"1997-07-16T19:20:30.4555555+1400\"" }; yield return new object[] { "\"1997-07-16T19:20:30.4555555-1400\"" }; - // Proper format but invalid calendar date, time, or time zone designator fields yield return new object[] { "\"1997-00-16T19:20:30.4555555\"" }; yield return new object[] { "\"1997-07-16T25:20:30.4555555\"" }; @@ -215,6 +214,7 @@ public static IEnumerable InvalidISO8601Tests() yield return new object[] { "\"1997-07-16T19:20:30.45555555550000000\"" }; yield return new object[] { "\"1997-07-16T19:20:30.45555555555555555\"" }; yield return new object[] { "\"1997-07-16T19:20:30.45555555555555555555\"" }; + yield return new object[] { "\"1997-07-16T19:20:30.4555555555555555+01:300\"" }; // Hex strings diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index 57e5e4c2a98fe..3b00db0e39441 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -451,6 +451,7 @@ private static void DeserializeLongJsonString(int stringLength) [Theory] [InlineData("23:59:59")] + [InlineData("\\u002D23:59:59", "-23:59:59")] [InlineData("\\u0032\\u0033\\u003A\\u0035\\u0039\\u003A\\u0035\\u0039", "23:59:59")] [InlineData("23:59:59.9", "23:59:59.9000000")] [InlineData("23:59:59.9999999")] @@ -484,6 +485,8 @@ public static void TimeSpan_Read_KnownDifferences() } [Theory] + [InlineData("\\t23:59:59")] // Otherwise valid but has leading whitespace + [InlineData("23:59:59 ")] // Otherwise valid but has trailing whitespace [InlineData("24:00:00")] [InlineData("\\u0032\\u0034\\u003A\\u0030\\u0030\\u003A\\u0030\\u0030")] [InlineData("00:60:00")] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonReaderTests.TryGet.Date.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonReaderTests.TryGet.Date.cs index 1df529baf9503..bd4cbdb864e77 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonReaderTests.TryGet.Date.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Utf8JsonReaderTests.TryGet.Date.cs @@ -1,6 +1,7 @@ // 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.Globalization; using Xunit; @@ -201,5 +202,55 @@ public static void TestingDateTimeMaxValue() // Test upstream serializer. Assert.Equal(DateTime.Parse(expectedString), JsonSerializer.Deserialize(jsonString)); } + + [Theory] + [MemberData(nameof(JsonDateTimeTestData.InvalidISO8601Tests), MemberType = typeof(JsonDateTimeTestData))] + public static void TryGetDateTime_HasValueSequence_False(string testString) + { + static void test(string testString, bool isFinalBlock) + { + byte[] dataUtf8 = Encoding.UTF8.GetBytes(testString); + ReadOnlySequence sequence = JsonTestHelper.GetSequence(dataUtf8, 1); + var json = new Utf8JsonReader(sequence, isFinalBlock: isFinalBlock, state: default); + + Assert.True(json.Read(), "json.Read()"); + Assert.Equal(JsonTokenType.String, json.TokenType); + Assert.True(json.HasValueSequence, "json.HasValueSequence"); + // If the string is empty, the ValueSequence is empty, because it contains all 0 bytes between the two characters + Assert.Equal(string.IsNullOrEmpty(testString), json.ValueSequence.IsEmpty); + Assert.False(json.TryGetDateTime(out DateTime actual), "json.TryGetDateTime(out DateTime actual)"); + Assert.Equal(DateTime.MinValue, actual); + + JsonTestHelper.AssertThrows(json, (jsonReader) => jsonReader.GetDateTime()); + } + + test(testString, isFinalBlock: true); + test(testString, isFinalBlock: false); + } + + [Theory] + [MemberData(nameof(JsonDateTimeTestData.InvalidISO8601Tests), MemberType = typeof(JsonDateTimeTestData))] + public static void TryGetDateTimeOffset_HasValueSequence_False(string testString) + { + static void test(string testString, bool isFinalBlock) + { + byte[] dataUtf8 = Encoding.UTF8.GetBytes(testString); + ReadOnlySequence sequence = JsonTestHelper.GetSequence(dataUtf8, 1); + var json = new Utf8JsonReader(sequence, isFinalBlock: isFinalBlock, state: default); + + Assert.True(json.Read(), "json.Read()"); + Assert.Equal(JsonTokenType.String, json.TokenType); + Assert.True(json.HasValueSequence, "json.HasValueSequence"); + // If the string is empty, the ValueSequence is empty, because it contains all 0 bytes between the two characters + Assert.Equal(string.IsNullOrEmpty(testString), json.ValueSequence.IsEmpty); + Assert.False(json.TryGetDateTimeOffset(out DateTimeOffset actual), "json.TryGetDateTimeOffset(out DateTimeOffset actual)"); + Assert.Equal(DateTimeOffset.MinValue, actual); + + JsonTestHelper.AssertThrows(json, (jsonReader) => jsonReader.GetDateTimeOffset()); + } + + test(testString, isFinalBlock: true); + test(testString, isFinalBlock: false); + } } } From f3e49ffc4b62f1237c7a27dce945856b0c527974 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 6 Jul 2021 16:37:16 -0700 Subject: [PATCH 10/10] Code review. --- .../System.Text.Json.Tests/Serialization/Value.ReadTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs index 3b00db0e39441..75ad7d1e6bae4 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Value.ReadTests.cs @@ -485,6 +485,7 @@ public static void TimeSpan_Read_KnownDifferences() } [Theory] + [InlineData("\t23:59:59")] // Otherwise valid but has invalid json character [InlineData("\\t23:59:59")] // Otherwise valid but has leading whitespace [InlineData("23:59:59 ")] // Otherwise valid but has trailing whitespace [InlineData("24:00:00")]