From 700444a996bfa12808ff28560c714968c5831469 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 23 Sep 2018 13:41:16 -0700 Subject: [PATCH] Add support for multiple ctors in JavaScriptDateTimeConverter --- Build/runbuild.cmd | 2 +- .../JavaScriptDateTimeConverterTests.cs | 73 ++++++++++++++++ .../Converters/JavaScriptDateTimeConverter.cs | 17 +--- Src/Newtonsoft.Json/JsonWriter.cs | 28 +----- .../Utilities/JavaScriptUtils.cs | 85 +++++++++++++++++++ 5 files changed, 165 insertions(+), 40 deletions(-) diff --git a/Build/runbuild.cmd b/Build/runbuild.cmd index 78f26407..6f18f69e 100644 --- a/Build/runbuild.cmd +++ b/Build/runbuild.cmd @@ -1,5 +1,5 @@ cls -powershell -Command "& { [Console]::WindowWidth = 150; [Console]::WindowHeight = 50; Start-Transcript '%~dp0\Temp\runbuild.txt'; Import-Module '%~dp0..\Tools\PSake\psake.psm1'; Invoke-psake '%~dp0..\Build\build.ps1' %*; Stop-Transcript; exit !($psake.build_success); }" +powershell -Command "& { Start-Transcript '%~dp0\Temp\runbuild.txt'; Import-Module '%~dp0..\Tools\PSake\psake.psm1'; Invoke-psake '%~dp0..\Build\build.ps1' %*; Stop-Transcript; exit !($psake.build_success); }" ECHO %ERRORLEVEL% EXIT /B %ERRORLEVEL% \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Converters/JavaScriptDateTimeConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/JavaScriptDateTimeConverterTests.cs index 23b38b90..bf4db9c6 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/JavaScriptDateTimeConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/JavaScriptDateTimeConverterTests.cs @@ -124,6 +124,79 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual(new DateTime(2000, 12, 15, 22, 11, 3, 55, DateTimeKind.Utc), result); } + [Test] + public void DeserializeDateTime_MultipleArguments() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + + DateTime result; + + result = JsonConvert.DeserializeObject("new Date(2000, 11)", converter); + Assert.AreEqual(new DateTime(2000, 12, 1, 0, 0, 0, 0, DateTimeKind.Utc), result); + + result = JsonConvert.DeserializeObject("new Date(2000, 11, 12)", converter); + Assert.AreEqual(new DateTime(2000, 12, 12, 0, 0, 0, 0, DateTimeKind.Utc), result); + + result = JsonConvert.DeserializeObject("new Date(2000, 11, 12, 20)", converter); + Assert.AreEqual(new DateTime(2000, 12, 12, 20, 0, 0, 0, DateTimeKind.Utc), result); + + result = JsonConvert.DeserializeObject("new Date(2000, 11, 12, 20, 1)", converter); + Assert.AreEqual(new DateTime(2000, 12, 12, 20, 1, 0, 0, DateTimeKind.Utc), result); + + result = JsonConvert.DeserializeObject("new Date(2000, 11, 12, 20, 1, 2)", converter); + Assert.AreEqual(new DateTime(2000, 12, 12, 20, 1, 2, 0, DateTimeKind.Utc), result); + + result = JsonConvert.DeserializeObject("new Date(2000, 11, 12, 20, 1, 2, 3)", converter); + Assert.AreEqual(new DateTime(2000, 12, 12, 20, 1, 2, 3, DateTimeKind.Utc), result); + + result = JsonConvert.DeserializeObject("new Date(2000, 11, 1, 0, 0, 0, 0)", converter); + Assert.AreEqual(new DateTime(2000, 12, 1, 0, 0, 0, 0, DateTimeKind.Utc), result); + } + + [Test] + public void DeserializeDateTime_TooManyArguments() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + + ExceptionAssert.Throws(() => + { + JsonConvert.DeserializeObject("new Date(1, 2, 3, 4, 5, 6, 7, 8)", converter); + }, "Unexpected number of arguments when reading date constructor. Path '', line 1, position 32."); + } + + [Test] + public void DeserializeDateTime_NoArguments() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + + ExceptionAssert.Throws(() => + { + JsonConvert.DeserializeObject("new Date()", converter); + }, "Date constructor has no arguments. Path '', line 1, position 10."); + } + + [Test] + public void DeserializeDateTime_NotArgumentsNotClosed() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + + ExceptionAssert.Throws(() => + { + JsonConvert.DeserializeObject("new Date(", converter); + }, "Unexpected end when reading date constructor. Path '', line 1, position 9."); + } + + [Test] + public void DeserializeDateTime_NotClosed() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + + ExceptionAssert.Throws(() => + { + JsonConvert.DeserializeObject("new Date(2, 3", converter); + }, "Unexpected end when reading date constructor. Path '[1]', line 1, position 13."); + } + [Test] public void ConverterList() { diff --git a/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs index 146c3ea5..24194b76 100644 --- a/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs @@ -91,22 +91,9 @@ namespace Newtonsoft.Json.Converters throw JsonSerializationException.Create(reader, "Unexpected token or value when parsing date. Token: {0}, Value: {1}".FormatWith(CultureInfo.InvariantCulture, reader.TokenType, reader.Value)); } - reader.Read(); - - if (reader.TokenType != JsonToken.Integer) + if (!JavaScriptUtils.TryGetDateFromConstructorJson(reader, out DateTime d, out string errorMessage)) { - throw JsonSerializationException.Create(reader, "Unexpected token parsing date. Expected Integer, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType)); - } - - long ticks = (long)reader.Value; - - DateTime d = DateTimeUtils.ConvertJavaScriptTicksToDateTime(ticks); - - reader.Read(); - - if (reader.TokenType != JsonToken.EndConstructor) - { - throw JsonSerializationException.Create(reader, "Unexpected token parsing date. Expected EndConstructor, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType)); + throw JsonSerializationException.Create(reader, errorMessage); } #if HAVE_DATE_TIME_OFFSET diff --git a/Src/Newtonsoft.Json/JsonWriter.cs b/Src/Newtonsoft.Json/JsonWriter.cs index b79ffda8..25c68b25 100644 --- a/Src/Newtonsoft.Json/JsonWriter.cs +++ b/Src/Newtonsoft.Json/JsonWriter.cs @@ -696,32 +696,12 @@ namespace Newtonsoft.Json private void WriteConstructorDate(JsonReader reader) { - List DateParams = new List(); - while (true) + if (!JavaScriptUtils.TryGetDateFromConstructorJson(reader, out DateTime dateTime, out string errorMessage)) { - if (!reader.Read()) - throw JsonWriterException.Create(this, "Unexpected end when reading date constructor.", null); - if (reader.TokenType == JsonToken.EndConstructor) - break; - if (reader.TokenType != JsonToken.Integer) - throw JsonWriterException.Create(this, "Unexpected token when reading date constructor. Expected Integer, got " + reader.TokenType, null); - DateParams.Add((long)reader.Value); - if (DateParams.Count > 7) - throw JsonWriterException.Create(this, "Unexpected token when reading date constructor. Wrong number of arguments", null); + throw JsonWriterException.Create(this, errorMessage, null); } - DateTime date; - if (DateParams.Count == 1) - { - date = DateTimeUtils.ConvertJavaScriptTicksToDateTime(DateParams[0]); - } - else if (DateParams.Count == 7) //Add support: new Date(2018,5,26,11,14,31,36) - { - date = new DateTime((int)DateParams[0], (int)DateParams[1] + 1, (int)DateParams[2], (int)DateParams[3], - (int)DateParams[4], (int)DateParams[5], (int)DateParams[6]); - } - else - throw JsonWriterException.Create(this, "Unexpected token when reading date constructor. Wrong number of arguments", null); - WriteValue(date); + + WriteValue(dateTime); } private void WriteEnd(JsonContainerType type) diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index 890d5810..a50fc631 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -564,5 +564,90 @@ namespace Newtonsoft.Json.Utilities } } #endif + + public static bool TryGetDateFromConstructorJson(JsonReader reader, out DateTime dateTime, out string errorMessage) + { + dateTime = default(DateTime); + errorMessage = null; + + if (!TryGetDateConstructorValue(reader, out long? t1, out errorMessage) || t1 == null) + { + errorMessage = errorMessage ?? "Date constructor has no arguments."; + return false; + } + if (!TryGetDateConstructorValue(reader, out long? t2, out errorMessage)) + { + return false; + } + else if (t2 != null) + { + // Only create a list when there is more than one argument + List dateArgs = new List + { + t1.Value, + t2.Value + }; + while (true) + { + if (!TryGetDateConstructorValue(reader, out long? integer, out errorMessage)) + { + return false; + } + else if (integer != null) + { + dateArgs.Add(integer.Value); + } + else + { + break; + } + } + + if (dateArgs.Count > 7) + { + errorMessage = "Unexpected number of arguments when reading date constructor."; + return false; + } + + // Pad args out to the number used by the ctor + while (dateArgs.Count < 7) + { + dateArgs.Add(0); + } + + dateTime = new DateTime((int)dateArgs[0], (int)dateArgs[1] + 1, dateArgs[2] == 0 ? 1 : (int)dateArgs[2], + (int)dateArgs[3], (int)dateArgs[4], (int)dateArgs[5], (int)dateArgs[6]); + } + else + { + dateTime = DateTimeUtils.ConvertJavaScriptTicksToDateTime(t1.Value); + } + + return true; + } + + private static bool TryGetDateConstructorValue(JsonReader reader, out long? integer, out string errorMessage) + { + integer = null; + errorMessage = null; + + if (!reader.Read()) + { + errorMessage = "Unexpected end when reading date constructor."; + return false; + } + if (reader.TokenType == JsonToken.EndConstructor) + { + return true; + } + if (reader.TokenType != JsonToken.Integer) + { + errorMessage = "Unexpected token when reading date constructor. Expected Integer, got " + reader.TokenType; + return false; + } + + integer = (long)reader.Value; + return true; + } } } \ No newline at end of file