From fd760e33898cc6b3badaf4a8b77a466139b87fac Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 4 Mar 2012 18:10:20 +0600 Subject: -Added DateFormatHandling to control whether dates are written in the MS format or ISO format, with ISO as the default -Added DateTimeZoneHandling to control reading and writing DateTime time zone details -Added reader/writer specific Formatting, DateFormatHandling and DateTimeZoneHandling to JsonSerializerSettings -Added feature of reader/writer specific settings to be set on JsonReader/JsonWriter when serializing -Added ReadAsDate to JsonReader -Added ReadAsString to JsonReader -Changed JsonTextReader to automatically read ISO strings as dates -Refactored ReadAsXXX methods and reduce code redundancy -Removed unused JsonDateTimeSerializationMode --- .../Converters/DataSetConverterTests.cs | 10 +- .../Converters/DataTableConverterTests.cs | 2 +- .../Converters/IsoDateTimeConverterTests.cs | 40 -- Src/Newtonsoft.Json.Tests/JsonConvertTest.cs | 316 +++++++++++++++- Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs | 8 +- Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs | 8 +- Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs | 8 +- Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs | 4 +- Src/Newtonsoft.Json.Tests/PerformanceTests.cs | 71 +++- .../Properties/AssemblyInfo.cs | 2 +- .../CamelCasePropertyNamesContractResolverTests.cs | 10 +- .../Serialization/ContractResolverTests.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 226 ++++++++++-- .../Serialization/NullValueHandlingTests.cs | 2 +- .../SerializationErrorHandlingTests.cs | 4 +- .../Serialization/TypeNameHandlingTests.cs | 6 +- Src/Newtonsoft.Json.Tests/TestFixtureBase.cs | 11 + Src/Newtonsoft.Json/Bson/BsonReader.cs | 155 ++------ .../Converters/IsoDateTimeConverter.cs | 10 + .../Converters/JsonDateTimeSerializationMode.cs | 30 -- Src/Newtonsoft.Json/DateFormatHandling.cs | 8 + Src/Newtonsoft.Json/DateTimeZoneHandling.cs | 28 ++ Src/Newtonsoft.Json/Formatting.cs | 17 + Src/Newtonsoft.Json/JsonConvert.cs | 175 +++++++-- Src/Newtonsoft.Json/JsonPosition.cs | 70 ++++ Src/Newtonsoft.Json/JsonReader.cs | 410 +++++++++++++++++---- Src/Newtonsoft.Json/JsonSerializer.cs | 90 ++++- Src/Newtonsoft.Json/JsonSerializerSettings.cs | 37 ++ Src/Newtonsoft.Json/JsonTextReader.cs | 393 +++++++------------- Src/Newtonsoft.Json/JsonTextWriter.cs | 7 +- Src/Newtonsoft.Json/JsonValidatingReader.cs | 24 ++ Src/Newtonsoft.Json/JsonWriter.cs | 68 +--- Src/Newtonsoft.Json/Linq/JTokenReader.cs | 175 ++------- Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj | 9 +- Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj | 9 +- .../Newtonsoft.Json.Silverlight.csproj | 6 +- .../Newtonsoft.Json.WindowsPhone.csproj | 6 +- Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 6 +- Src/Newtonsoft.Json/Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json/Serialization/JsonContract.cs | 19 +- .../Serialization/JsonSerializerInternalReader.cs | 6 + .../Serialization/JsonSerializerProxy.cs | 19 + Src/Newtonsoft.Json/WriteState.cs | 41 +++ 43 files changed, 1726 insertions(+), 824 deletions(-) delete mode 100644 Src/Newtonsoft.Json/Converters/JsonDateTimeSerializationMode.cs create mode 100644 Src/Newtonsoft.Json/DateFormatHandling.cs create mode 100644 Src/Newtonsoft.Json/DateTimeZoneHandling.cs create mode 100644 Src/Newtonsoft.Json/Formatting.cs create mode 100644 Src/Newtonsoft.Json/JsonPosition.cs create mode 100644 Src/Newtonsoft.Json/WriteState.cs diff --git a/Src/Newtonsoft.Json.Tests/Converters/DataSetConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/DataSetConverterTests.cs index 058072b..c333acc 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/DataSetConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/DataSetConverterTests.cs @@ -170,7 +170,7 @@ namespace Newtonsoft.Json.Tests.Converters ] }"; - DataSet ds = JsonConvert.DeserializeObject(json, new IsoDateTimeConverter()); + DataSet ds = JsonConvert.DeserializeObject(json); Assert.IsNotNull(ds); Assert.AreEqual(2, ds.Tables.Count); @@ -187,7 +187,7 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual("TimeSpanCol", dt.Columns[3].ColumnName); Assert.AreEqual(typeof(string), dt.Columns[3].DataType); Assert.AreEqual("DateTimeCol", dt.Columns[4].ColumnName); - Assert.AreEqual(typeof(string), dt.Columns[4].DataType); + Assert.AreEqual(typeof(DateTime), dt.Columns[4].DataType); Assert.AreEqual("DecimalCol", dt.Columns[5].ColumnName); Assert.AreEqual(typeof(double), dt.Columns[5].DataType); @@ -270,7 +270,7 @@ namespace Newtonsoft.Json.Tests.Converters ""int32Col"": 1, ""booleanCol"": true, ""timeSpanCol"": ""10.22:10:15.1000000"", - ""dateTimeCol"": ""\/Date(978048000000)\/"", + ""dateTimeCol"": ""2000-12-29T00:00:00Z"", ""decimalCol"": 64.0021 }, { @@ -278,7 +278,7 @@ namespace Newtonsoft.Json.Tests.Converters ""int32Col"": 2, ""booleanCol"": true, ""timeSpanCol"": ""10.22:10:15.1000000"", - ""dateTimeCol"": ""\/Date(978048000000)\/"", + ""dateTimeCol"": ""2000-12-29T00:00:00Z"", ""decimalCol"": 64.0021 } ], @@ -288,7 +288,7 @@ namespace Newtonsoft.Json.Tests.Converters ""int32Col"": 1, ""booleanCol"": true, ""timeSpanCol"": ""10.22:10:15.1000000"", - ""dateTimeCol"": ""\/Date(978048000000)\/"", + ""dateTimeCol"": ""2000-12-29T00:00:00Z"", ""decimalCol"": 64.0021 } ] diff --git a/Src/Newtonsoft.Json.Tests/Converters/DataTableConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/DataTableConverterTests.cs index 0a614e2..fc4a686 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/DataTableConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/DataTableConverterTests.cs @@ -97,7 +97,7 @@ namespace Newtonsoft.Json.Tests.Converters ""Int32Col"": 2147483647, ""BooleanCol"": true, ""TimeSpanCol"": ""10.22:10:15.1000000"", - ""DateTimeCol"": ""\/Date(978048000000)\/"", + ""DateTimeCol"": ""2000-12-29T00:00:00Z"", ""DecimalCol"": 64.0021 } ]", json); diff --git a/Src/Newtonsoft.Json.Tests/Converters/IsoDateTimeConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/IsoDateTimeConverterTests.cs index 3345423..e7ac34d 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/IsoDateTimeConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/IsoDateTimeConverterTests.cs @@ -201,46 +201,6 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual(@"{""PreField"":""Pre"",""DateTimeField"":null,""DateTimeOffsetField"":null,""PostField"":""Post""}", json); } - [Test] - public void DeserializeUTC() - { - DateTimeTestClass c = - JsonConvert.DeserializeObject(@"{""PreField"":""Pre"",""DateTimeField"":""2008-12-12T12:12:12Z"",""DateTimeOffsetField"":""2008-12-12T12:12:12Z"",""PostField"":""Post""}", new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AssumeUniversal }); - - Assert.AreEqual(new DateTime(2008, 12, 12, 12, 12, 12, 0, DateTimeKind.Utc).ToLocalTime(), c.DateTimeField); - Assert.AreEqual(new DateTimeOffset(2008, 12, 12, 12, 12, 12, 0, TimeSpan.Zero), c.DateTimeOffsetField); - Assert.AreEqual("Pre", c.PreField); - Assert.AreEqual("Post", c.PostField); - - DateTimeTestClass c2 = - JsonConvert.DeserializeObject(@"{""PreField"":""Pre"",""DateTimeField"":""2008-01-01T01:01:01Z"",""DateTimeOffsetField"":""2008-01-01T01:01:01Z"",""PostField"":""Post""}", new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AssumeUniversal }); - - Assert.AreEqual(new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Utc).ToLocalTime(), c2.DateTimeField); - Assert.AreEqual(new DateTimeOffset(2008, 1, 1, 1, 1, 1, 0, TimeSpan.Zero), c2.DateTimeOffsetField); - Assert.AreEqual("Pre", c2.PreField); - Assert.AreEqual("Post", c2.PostField); - } - - [Test] - public void NullableDeserializeUTC() - { - NullableDateTimeTestClass c = - JsonConvert.DeserializeObject(@"{""PreField"":""Pre"",""DateTimeField"":""2008-12-12T12:12:12Z"",""DateTimeOffsetField"":""2008-12-12T12:12:12Z"",""PostField"":""Post""}", new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AssumeUniversal }); - - Assert.AreEqual(new DateTime(2008, 12, 12, 12, 12, 12, 0, DateTimeKind.Utc).ToLocalTime(), c.DateTimeField); - Assert.AreEqual(new DateTimeOffset(2008, 12, 12, 12, 12, 12, 0, TimeSpan.Zero), c.DateTimeOffsetField); - Assert.AreEqual("Pre", c.PreField); - Assert.AreEqual("Post", c.PostField); - - NullableDateTimeTestClass c2 = - JsonConvert.DeserializeObject(@"{""PreField"":""Pre"",""DateTimeField"":null,""DateTimeOffsetField"":null,""PostField"":""Post""}", new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AssumeUniversal }); - - Assert.AreEqual(null, c2.DateTimeField); - Assert.AreEqual(null, c2.DateTimeOffsetField); - Assert.AreEqual("Pre", c2.PreField); - Assert.AreEqual("Post", c2.PostField); - } - [Test] public void NullableDeserializeEmptyString() { diff --git a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs index 91c11b9..f1348c2 100644 --- a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs @@ -24,6 +24,11 @@ #endregion using System; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Utilities; using NUnit.Framework; @@ -233,11 +238,17 @@ now brown cow?", '"', true); Assert.AreEqual("1", JsonConvert.ToString(value)); value = new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc); - Assert.AreEqual(@"""\/Date(0)\/""", JsonConvert.ToString(value)); + Assert.AreEqual(@"""1970-01-01T00:00:00Z""", JsonConvert.ToString(value)); + + value = new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc); + Assert.AreEqual(@"""\/Date(0)\/""", JsonConvert.ToString((DateTime)value, DateFormatHandling.MicrosoftDateFormat, DateTimeZoneHandling.RoundtripKind)); #if !PocketPC && !NET20 value = new DateTimeOffset(JsonConvert.InitialJavaScriptDateTicks, TimeSpan.Zero); - Assert.AreEqual(@"""\/Date(0+0000)\/""", JsonConvert.ToString(value)); + Assert.AreEqual(@"""1970-01-01T00:00:00+00:00""", JsonConvert.ToString(value)); + + value = new DateTimeOffset(JsonConvert.InitialJavaScriptDateTicks, TimeSpan.Zero); + Assert.AreEqual(@"""\/Date(0+0000)\/""", JsonConvert.ToString((DateTimeOffset)value, DateFormatHandling.MicrosoftDateFormat)); #endif value = null; @@ -305,9 +316,9 @@ now brown cow?", '"', true); Assert.AreEqual("-1.0", JsonConvert.ToString(-1d)); Assert.AreEqual("1.01", JsonConvert.ToString(1.01)); Assert.AreEqual("1.001", JsonConvert.ToString(1.001)); - Assert.AreEqual(JsonConvert.PositiveInfinity, JsonConvert.ToString(double.PositiveInfinity)); - Assert.AreEqual(JsonConvert.NegativeInfinity, JsonConvert.ToString(double.NegativeInfinity)); - Assert.AreEqual(JsonConvert.NaN, JsonConvert.ToString(double.NaN)); + Assert.AreEqual(JsonConvert.PositiveInfinity, JsonConvert.ToString(Double.PositiveInfinity)); + Assert.AreEqual(JsonConvert.NegativeInfinity, JsonConvert.ToString(Double.NegativeInfinity)); + Assert.AreEqual(JsonConvert.NaN, JsonConvert.ToString(Double.NaN)); } [Test] @@ -325,8 +336,8 @@ now brown cow?", '"', true); Assert.AreEqual("1.0", JsonConvert.ToString(1m)); Assert.AreEqual("1.01", JsonConvert.ToString(1.01m)); Assert.AreEqual("1.001", JsonConvert.ToString(1.001m)); - Assert.AreEqual("79228162514264337593543950335.0", JsonConvert.ToString(decimal.MaxValue)); - Assert.AreEqual("-79228162514264337593543950335.0", JsonConvert.ToString(decimal.MinValue)); + Assert.AreEqual("79228162514264337593543950335.0", JsonConvert.ToString(Decimal.MaxValue)); + Assert.AreEqual("-79228162514264337593543950335.0", JsonConvert.ToString(Decimal.MinValue)); } [Test] @@ -338,5 +349,296 @@ now brown cow?", '"', true); string json = JsonConvert.ToString(v); Assert.AreEqual(@"""It's a good day\r\n\""sunshine\""""", json); } + + [Test] + public void WriteDateTime() + { + DateTimeResult result = null; + + result = TestDateTime("DateTime Max", DateTime.MaxValue); + Assert.AreEqual("9999-12-31T23:59:59.9999999", result.IsoDateRoundtrip); + Assert.AreEqual("9999-12-31T23:59:59.9999999" + GetOffset(DateTime.MaxValue, DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("9999-12-31T23:59:59.9999999", result.IsoDateUnspecified); + Assert.AreEqual("9999-12-31T23:59:59.9999999Z", result.IsoDateUtc); + Assert.AreEqual(@"\/Date(253402300799999)\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(253402300799999" + GetOffset(DateTime.MaxValue, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(253402300799999)\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(253402300799999)\/", result.MsDateUtc); + + DateTime year2000local = new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Local); + string localToUtcDate = year2000local.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK"); + + result = TestDateTime("DateTime Local", year2000local); + Assert.AreEqual("2000-01-01T01:01:01" + GetOffset(year2000local, DateFormatHandling.IsoDateFormat), result.IsoDateRoundtrip); + Assert.AreEqual("2000-01-01T01:01:01" + GetOffset(year2000local, DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("2000-01-01T01:01:01", result.IsoDateUnspecified); + Assert.AreEqual(localToUtcDate, result.IsoDateUtc); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(year2000local) + GetOffset(year2000local, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(year2000local) + GetOffset(year2000local, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(year2000local) + GetOffset(year2000local, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(year2000local) + @")\/", result.MsDateUtc); + + DateTime millisecondsLocal = new DateTime(2000, 1, 1, 1, 1, 1, 999, DateTimeKind.Local); + localToUtcDate = millisecondsLocal.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK"); + + result = TestDateTime("DateTime Local with milliseconds", millisecondsLocal); + Assert.AreEqual("2000-01-01T01:01:01.999" + GetOffset(millisecondsLocal, DateFormatHandling.IsoDateFormat), result.IsoDateRoundtrip); + Assert.AreEqual("2000-01-01T01:01:01.999" + GetOffset(millisecondsLocal, DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("2000-01-01T01:01:01.999", result.IsoDateUnspecified); + Assert.AreEqual(localToUtcDate, result.IsoDateUtc); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(millisecondsLocal) + GetOffset(millisecondsLocal, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(millisecondsLocal) + GetOffset(millisecondsLocal, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(millisecondsLocal) + GetOffset(millisecondsLocal, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(millisecondsLocal) + @")\/", result.MsDateUtc); + + DateTime ticksLocal = new DateTime(634663873826822481, DateTimeKind.Local); + localToUtcDate = ticksLocal.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFFK"); + + result = TestDateTime("DateTime Local with ticks", ticksLocal); + Assert.AreEqual("2012-03-03T16:03:02.6822481" + GetOffset(ticksLocal, DateFormatHandling.IsoDateFormat), result.IsoDateRoundtrip); + Assert.AreEqual("2012-03-03T16:03:02.6822481" + GetOffset(ticksLocal, DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("2012-03-03T16:03:02.6822481", result.IsoDateUnspecified); + Assert.AreEqual(localToUtcDate, result.IsoDateUtc); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(ticksLocal) + GetOffset(ticksLocal, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(ticksLocal) + GetOffset(ticksLocal, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(ticksLocal) + GetOffset(ticksLocal, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(ticksLocal) + @")\/", result.MsDateUtc); + + DateTime year2000Unspecified = new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Unspecified); + + result = TestDateTime("DateTime Unspecified", year2000Unspecified); + Assert.AreEqual("2000-01-01T01:01:01", result.IsoDateRoundtrip); + Assert.AreEqual("2000-01-01T01:01:01" + GetOffset(year2000Unspecified, DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("2000-01-01T01:01:01", result.IsoDateUnspecified); + Assert.AreEqual("2000-01-01T01:01:01Z", result.IsoDateUtc); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(year2000Unspecified) + GetOffset(year2000Unspecified, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(year2000Unspecified) + GetOffset(year2000Unspecified, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(year2000Unspecified) + GetOffset(year2000Unspecified, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(year2000Unspecified.ToLocalTime()) + @")\/", result.MsDateUtc); + + DateTime year2000Utc = new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Utc); + string utcTolocalDate = year2000Utc.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:ss"); + + result = TestDateTime("DateTime Utc", year2000Utc); + Assert.AreEqual("2000-01-01T01:01:01Z", result.IsoDateRoundtrip); + Assert.AreEqual(utcTolocalDate + GetOffset(year2000Utc, DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("2000-01-01T01:01:01", result.IsoDateUnspecified); + Assert.AreEqual("2000-01-01T01:01:01Z", result.IsoDateUtc); + Assert.AreEqual(@"\/Date(946688461000)\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(946688461000" + GetOffset(year2000Utc, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(DateTime.SpecifyKind(year2000Utc, DateTimeKind.Unspecified)) + GetOffset(year2000Utc, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(946688461000)\/", result.MsDateUtc); + + DateTime unixEpoc = new DateTime(621355968000000000, DateTimeKind.Utc); + utcTolocalDate = unixEpoc.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:ss"); + + result = TestDateTime("DateTime Unix Epoc", unixEpoc); + Assert.AreEqual("1970-01-01T00:00:00Z", result.IsoDateRoundtrip); + Assert.AreEqual(utcTolocalDate + GetOffset(unixEpoc, DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("1970-01-01T00:00:00", result.IsoDateUnspecified); + Assert.AreEqual("1970-01-01T00:00:00Z", result.IsoDateUtc); + Assert.AreEqual(@"\/Date(0)\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(0" + GetOffset(unixEpoc, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(" + JsonConvert.ConvertDateTimeToJavaScriptTicks(DateTime.SpecifyKind(unixEpoc, DateTimeKind.Unspecified)) + GetOffset(unixEpoc, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(0)\/", result.MsDateUtc); + + result = TestDateTime("DateTime Min", DateTime.MinValue); + Assert.AreEqual("0001-01-01T00:00:00", result.IsoDateRoundtrip); + Assert.AreEqual("0001-01-01T00:00:00" + GetOffset(DateTime.MinValue, DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("0001-01-01T00:00:00", result.IsoDateUnspecified); + Assert.AreEqual("0001-01-01T00:00:00Z", result.IsoDateUtc); + Assert.AreEqual(@"\/Date(-62135596800000)\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(-62135596800000" + GetOffset(DateTime.MinValue, DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(-62135596800000)\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(-62135596800000)\/", result.MsDateUtc); + + result = TestDateTime("DateTime Default", default(DateTime)); + Assert.AreEqual("0001-01-01T00:00:00", result.IsoDateRoundtrip); + Assert.AreEqual("0001-01-01T00:00:00" + GetOffset(default(DateTime), DateFormatHandling.IsoDateFormat), result.IsoDateLocal); + Assert.AreEqual("0001-01-01T00:00:00", result.IsoDateUnspecified); + Assert.AreEqual("0001-01-01T00:00:00Z", result.IsoDateUtc); + Assert.AreEqual(@"\/Date(-62135596800000)\/", result.MsDateRoundtrip); + Assert.AreEqual(@"\/Date(-62135596800000" + GetOffset(default(DateTime), DateFormatHandling.MicrosoftDateFormat) + @")\/", result.MsDateLocal); + Assert.AreEqual(@"\/Date(-62135596800000)\/", result.MsDateUnspecified); + Assert.AreEqual(@"\/Date(-62135596800000)\/", result.MsDateUtc); + +#if !NET20 + result = TestDateTime("DateTimeOffset TimeSpan Zero", new DateTimeOffset(2000, 1, 1, 1, 1, 1, TimeSpan.Zero)); + Assert.AreEqual("2000-01-01T01:01:01+00:00", result.IsoDateRoundtrip); + Assert.AreEqual(@"\/Date(946688461000+0000)\/", result.MsDateRoundtrip); + + result = TestDateTime("DateTimeOffset TimeSpan 1 hour", new DateTimeOffset(2000, 1, 1, 1, 1, 1, TimeSpan.FromHours(1))); + Assert.AreEqual("2000-01-01T01:01:01+01:00", result.IsoDateRoundtrip); + Assert.AreEqual(@"\/Date(946684861000+0100)\/", result.MsDateRoundtrip); + + result = TestDateTime("DateTimeOffset TimeSpan 1.5 hour", new DateTimeOffset(2000, 1, 1, 1, 1, 1, TimeSpan.FromHours(1.5))); + Assert.AreEqual("2000-01-01T01:01:01+01:30", result.IsoDateRoundtrip); + Assert.AreEqual(@"\/Date(946683061000+0130)\/", result.MsDateRoundtrip); + + result = TestDateTime("DateTimeOffset TimeSpan 13 hour", new DateTimeOffset(2000, 1, 1, 1, 1, 1, TimeSpan.FromHours(13))); + Assert.AreEqual("2000-01-01T01:01:01+13:00", result.IsoDateRoundtrip); + Assert.AreEqual(@"\/Date(946641661000+1300)\/", result.MsDateRoundtrip); + + result = TestDateTime("DateTimeOffset TimeSpan with ticks", new DateTimeOffset(634663873826822481, TimeSpan.Zero)); + Assert.AreEqual("2012-03-03T16:03:02.6822481+00:00", result.IsoDateRoundtrip); + Assert.AreEqual(@"\/Date(1330790582682+0000)\/", result.MsDateRoundtrip); + + result = TestDateTime("DateTimeOffset Min", DateTimeOffset.MinValue); + Assert.AreEqual("0001-01-01T00:00:00+00:00", result.IsoDateRoundtrip); + Assert.AreEqual(@"\/Date(-62135596800000+0000)\/", result.MsDateRoundtrip); + + result = TestDateTime("DateTimeOffset Max", DateTimeOffset.MaxValue); + Assert.AreEqual("9999-12-31T23:59:59.9999999+00:00", result.IsoDateRoundtrip); + Assert.AreEqual(@"\/Date(253402300799999+0000)\/", result.MsDateRoundtrip); + + result = TestDateTime("DateTimeOffset Default", default(DateTimeOffset)); + Assert.AreEqual("0001-01-01T00:00:00+00:00", result.IsoDateRoundtrip); + Assert.AreEqual(@"\/Date(-62135596800000+0000)\/", result.MsDateRoundtrip); +#endif + } + + public class DateTimeResult + { + public string IsoDateRoundtrip { get; set; } + public string IsoDateLocal { get; set; } + public string IsoDateUnspecified { get; set; } + public string IsoDateUtc { get; set; } + + public string MsDateRoundtrip { get; set; } + public string MsDateLocal { get; set; } + public string MsDateUnspecified { get; set; } + public string MsDateUtc { get; set; } + } + + private DateTimeResult TestDateTime(string name, T value) + { + Console.WriteLine(name); + + DateTimeResult result = new DateTimeResult(); + + result.IsoDateRoundtrip = TestDateTimeFormat(value, DateFormatHandling.IsoDateFormat, DateTimeZoneHandling.RoundtripKind); + if (value is DateTime) + { + result.IsoDateLocal = TestDateTimeFormat(value, DateFormatHandling.IsoDateFormat, DateTimeZoneHandling.Local); + result.IsoDateUnspecified = TestDateTimeFormat(value, DateFormatHandling.IsoDateFormat, DateTimeZoneHandling.Unspecified); + result.IsoDateUtc = TestDateTimeFormat(value, DateFormatHandling.IsoDateFormat, DateTimeZoneHandling.Utc); + } + + result.MsDateRoundtrip = TestDateTimeFormat(value, DateFormatHandling.MicrosoftDateFormat, DateTimeZoneHandling.RoundtripKind); + if (value is DateTime) + { + result.MsDateLocal = TestDateTimeFormat(value, DateFormatHandling.MicrosoftDateFormat, DateTimeZoneHandling.Local); + result.MsDateUnspecified = TestDateTimeFormat(value, DateFormatHandling.MicrosoftDateFormat, DateTimeZoneHandling.Unspecified); + result.MsDateUtc = TestDateTimeFormat(value, DateFormatHandling.MicrosoftDateFormat, DateTimeZoneHandling.Utc); + } + + TestDateTimeFormat(value, new IsoDateTimeConverter()); + + if (value is DateTime) + { + Console.WriteLine(XmlConvert.ToString((DateTime)(object)value, XmlDateTimeSerializationMode.RoundtripKind)); + } + else + { + Console.WriteLine(XmlConvert.ToString((DateTimeOffset)(object)value)); + } + +#if !NET20 + MemoryStream ms = new MemoryStream(); + DataContractSerializer s = new DataContractSerializer(typeof(T)); + s.WriteObject(ms, value); + string json = Encoding.UTF8.GetString(ms.ToArray(), 0, Convert.ToInt32(ms.Length)); + Console.WriteLine(json); +#endif + + Console.WriteLine(); + + return result; + } + + private static string TestDateTimeFormat(T value, DateFormatHandling format, DateTimeZoneHandling timeZoneHandling) + { + string date = null; + + if (value is DateTime) + { + date = JsonConvert.ToString((DateTime)(object)value, format, timeZoneHandling); + } + else + { +#if !NET20 + date = JsonConvert.ToString((DateTimeOffset)(object)value, format); +#endif + } + + Console.WriteLine(format.ToString("g") + "-" + timeZoneHandling.ToString("g") + ": " + date); + + if (timeZoneHandling == DateTimeZoneHandling.RoundtripKind) + { + T parsed = JsonConvert.DeserializeObject(date); + try + { + Assert.AreEqual(value, parsed); + } + catch (Exception ex) + { + long valueTicks = GetTicks(value); + long parsedTicks = GetTicks(parsed); + + valueTicks = (valueTicks/10000)*10000; + + Assert.AreEqual(valueTicks, parsedTicks); + } + } + + return date.Trim('"'); + } + + private static void TestDateTimeFormat(T value, JsonConverter converter) + { + string date = Write(value, converter); + + Console.WriteLine(converter.GetType().Name + ": " + date); + + T parsed = Read(date, converter); + + try + { + Assert.AreEqual(value, parsed); + } + catch (Exception ex) + { + // JavaScript ticks aren't as precise, recheck after rounding + long valueTicks = GetTicks(value); + long parsedTicks = GetTicks(parsed); + + valueTicks = (valueTicks / 10000) * 10000; + + Assert.AreEqual(valueTicks, parsedTicks); + } + } + + public static long GetTicks(object value) + { + return (value is DateTime) ? ((DateTime)value).Ticks : ((DateTimeOffset)value).Ticks; + } + + public static string Write(object value, JsonConverter converter) + { + StringWriter sw = new StringWriter(); + JsonTextWriter writer = new JsonTextWriter(sw); + converter.WriteJson(writer, value, null); + + writer.Flush(); + return sw.ToString(); + } + + public static T Read(string text, JsonConverter converter) + { + JsonTextReader reader = new JsonTextReader(new StringReader(text)); + reader.ReadAsString(); + + return (T)converter.ReadJson(reader, typeof(T), null, null); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs index 3488f99..173117b 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextReaderTest.cs @@ -586,7 +586,7 @@ Parameter name: reader")] } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected token when reading bytes: Boolean. Line 1, position 4.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading bytes. Unexpected token: Boolean. Line 1, position 4.")] public void ReadBytesWithBadCharacter() { JsonReader reader = new JsonTextReader(new StringReader(@"true")); @@ -1219,7 +1219,7 @@ bye", reader.Value); #endif [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected token when reading decimal: StartConstructor. Line 1, position 9.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading decimal. Unexpected token: StartConstructor. Line 1, position 9.")] public void ReadAsDecimalBadContent() { JsonTextReader reader = new JsonTextReader(new StringReader(@"new Date()")); @@ -1229,7 +1229,7 @@ bye", reader.Value); } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected token when reading bytes: StartConstructor. Line 1, position 9.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading bytes. Unexpected token: StartConstructor. Line 1, position 9.")] public void ReadAsBytesBadContent() { JsonTextReader reader = new JsonTextReader(new StringReader(@"new Date()")); @@ -1239,7 +1239,7 @@ bye", reader.Value); #if !NET20 [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Unexpected token when reading date: StartConstructor. Line 1, position 9.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading date. Unexpected token: StartConstructor. Line 1, position 9.")] public void ReadAsDateTimeOffsetBadContent() { JsonTextReader reader = new JsonTextReader(new StringReader(@"new Date()")); diff --git a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs index 97323a1..ad1effc 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs @@ -25,10 +25,14 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Serialization; using System.Text; +using System.Xml; using NUnit.Framework; using Newtonsoft.Json; using System.IO; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Tests @@ -133,9 +137,9 @@ namespace Newtonsoft.Json.Tests string expected; #if !PocketPC && !NET20 - expected = @"[null,""c"",null,true,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1.1,null,1.1,null,1.1,null,""\/Date(0)\/"",null,""\/Date(0+0000)\/""]"; + expected = @"[null,""c"",null,true,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1.1,null,1.1,null,1.1,null,""1970-01-01T00:00:00Z"",null,""1970-01-01T00:00:00+00:00""]"; #else - expected = @"[null,""c"",null,true,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1.1,null,1.1,null,1.1,null,""\/Date(0)\/""]"; + expected = @"[null,""c"",null,true,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1,null,1.1,null,1.1,null,1.1,null,""1970-01-01T00:00:00Z""]"; #endif Assert.AreEqual(expected, json); diff --git a/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs b/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs index 1a201ba..64136e6 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JTokenReaderTest.cs @@ -135,7 +135,7 @@ namespace Newtonsoft.Json.Tests.Linq } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading date. Expected date but got Boolean. Line 1, position 14.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading date. Unexpected token: Boolean. Line 1, position 14.")] public void ReadAsDateTimeOffsetBoolean() { string json = @"{""Offset"":true}"; @@ -294,7 +294,7 @@ namespace Newtonsoft.Json.Tests.Linq } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading bytes. Expected bytes but got Integer.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading bytes. Unexpected token: Integer.")] public void ReadBytesFailure() { JObject o = @@ -484,7 +484,7 @@ namespace Newtonsoft.Json.Tests.Linq } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading integer. Expected a number but got Boolean. Line 1, position 12.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading integer. Unexpected token: Boolean. Line 1, position 12.")] public void ReadAsInt32Boolean() { string json = @"{""Name"":true}"; @@ -543,7 +543,7 @@ namespace Newtonsoft.Json.Tests.Linq } [Test] - [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading decimal. Expected a number but got Boolean. Line 1, position 12.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading decimal. Unexpected token: Boolean. Line 1, position 12.")] public void ReadAsDecimalBoolean() { string json = @"{""Name"":true}"; diff --git a/Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs b/Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs index 56ea70f..e717cb4 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/LinqToJsonTest.cs @@ -349,13 +349,13 @@ keyword such as type of business."" ""Test3"": ""Test3Value"", ""Test4"": null }, - ""\/Date(971136000000)\/"", + ""2000-10-10T00:00:00Z"", 55, [ ""1"", 2, 3.0, - ""\/Date(-62030076711000)\/"" + ""0004-05-06T07:08:09Z"" ], new ConstructorName( ""param1"", diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 5b1d8ed..ca92859 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -39,8 +39,8 @@ namespace Newtonsoft.Json.Tests public class PerformanceTests : TestFixtureBase { - private const int Iterations = 100; - //private const int Iterations = 5000; + //private const int Iterations = 100; + private const int Iterations = 5000; #region Data @@ -56,12 +56,16 @@ namespace Newtonsoft.Json.Tests private const string JsonText = @"{""strings"":[null,""Markus egger ]><[, (2nd)"",null],""dictionary"":{""Val & asd1"":1,""Val2 & asd1"":3,""Val3 & asd1"":4},""Name"":""Rick"",""Now"":""\/Date(1262301136080+1300)\/"",""BigNumber"":34123123123.121,""Address1"":{""Street"":""fff Street"",""Phone"":""(503) 814-6335"",""Entered"":""\/Date(1264025536080+1300)\/""},""Addresses"":[{""Street"":""\u001farray<[, (2nd)"",null],""dictionary"":{""Val & asd1"":1,""Val2 & asd1"":3,""Val3 & asd1"":4},""Name"":""Rick"",""Now"":""2012-02-25T19:55:50.6095676+13:00"",""BigNumber"":34123123123.121,""Address1"":{""Street"":""fff Street"",""Phone"":""(503) 814-6335"",""Entered"":""2012-02-24T18:55:50.6095676+13:00""},""Addresses"":[{""Street"":""\u001farray(SerializeMethod.DataContractSerializer, XmlText); BenchmarkDeserializeMethod(SerializeMethod.BinaryFormatter, MiscellaneousUtils.HexToBytes(BinaryFormatterHex)); DeserializeTests(JsonText); + BenchmarkDeserializeMethod(SerializeMethod.JsonNetWithIsoConverter, JsonIsoText); BenchmarkDeserializeMethod(SerializeMethod.JsonNetBinary, MiscellaneousUtils.HexToBytes(BsonHex)); } @@ -471,6 +477,9 @@ namespace Newtonsoft.Json.Tests case SerializeMethod.JsonNet: json = JsonConvert.SerializeObject(value); break; + case SerializeMethod.JsonNetWithIsoConverter: + json = JsonConvert.SerializeObject(value, new IsoDateTimeConverter()); + break; case SerializeMethod.JsonNetBinary: { MemoryStream ms = new MemoryStream(Buffer); @@ -538,7 +547,7 @@ namespace Newtonsoft.Json.Tests Console.WriteLine(); } - public T DeserializeJsonNet(string json) + public T DeserializeJsonNet(string json, bool isoDateTimeConverter) { Type type = typeof (T); @@ -546,9 +555,11 @@ namespace Newtonsoft.Json.Tests serializer.ObjectCreationHandling = Newtonsoft.Json.ObjectCreationHandling.Replace; serializer.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Ignore; serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + if (isoDateTimeConverter) + serializer.Converters.Add(new IsoDateTimeConverter()); - return (T) serializer.Deserialize(new StringReader(json), type); - + var value = (T) serializer.Deserialize(new StringReader(json), type); + return value; //JsonTextReader reader = new JsonTextReader(new StringReader(JsonText)); //while (reader.Read()) //{ @@ -591,7 +602,9 @@ namespace Newtonsoft.Json.Tests switch (method) { case SerializeMethod.JsonNet: - return DeserializeJsonNet((string) json); + return DeserializeJsonNet((string)json, false); + case SerializeMethod.JsonNetWithIsoConverter: + return DeserializeJsonNet((string)json, true); case SerializeMethod.JsonNetBinary: return DeserializeJsonNetBinary((byte[]) json); case SerializeMethod.BinaryFormatter: @@ -689,6 +702,52 @@ namespace Newtonsoft.Json.Tests return null; }, "JObject.ToString"); } + + [Test] + [Ignore] + public void NestedJToken() + { + Stopwatch sw; + for (int i = 10000; i <= 100000; i += 10000) + { + sw = new Stopwatch(); + sw.Start(); + JArray ija = new JArray(); + JToken ijt = ija; + for (int j = 0; j < i; j++) + { + JArray temp = new JArray(); + ija.Add(temp); + ija = temp; + } + ija.Add(1); + sw.Stop(); + Console.WriteLine("Created a JToken of depth {0} (using OM) in {1} millis", i, sw.ElapsedMilliseconds); + } + } + + [Test] + [Ignore] + public void NestedXElement() + { + Stopwatch sw; + for (int i = 10000; i <= 100000; i += 10000) + { + sw = new Stopwatch(); + sw.Start(); + XElement ija = new XElement("root"); + XElement ijt = ija; + for (int j = 0; j < i; j++) + { + XElement temp = new XElement("child"); + ija.Add(temp); + ija = temp; + } + ija.Add(1); + sw.Stop(); + Console.WriteLine("Created a XElement of depth {0} (using OM) in {1} millis", i, sw.ElapsedMilliseconds); + } + } } public class LargeRecursiveTestClass diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 4a503e7..e5c19f6 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -72,5 +72,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.0.8.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.0.8.14625")] +[assembly: AssemblyFileVersion("4.0.8.14704")] #endif diff --git a/Src/Newtonsoft.Json.Tests/Serialization/CamelCasePropertyNamesContractResolverTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/CamelCasePropertyNamesContractResolverTests.cs index b67a5cc..e59bc4e 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/CamelCasePropertyNamesContractResolverTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/CamelCasePropertyNamesContractResolverTests.cs @@ -53,8 +53,8 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(@"{ ""name"": ""Name!"", - ""birthDate"": ""\/Date(974764544000)\/"", - ""lastModified"": ""\/Date(974764544000)\/"" + ""birthDate"": ""2000-11-20T23:55:44Z"", + ""lastModified"": ""2000-11-20T23:55:44Z"" }", json); Person deserializedPerson = JsonConvert.DeserializeObject(json, new JsonSerializerSettings @@ -69,8 +69,8 @@ namespace Newtonsoft.Json.Tests.Serialization json = JsonConvert.SerializeObject(person, Formatting.Indented); Assert.AreEqual(@"{ ""Name"": ""Name!"", - ""BirthDate"": ""\/Date(974764544000)\/"", - ""LastModified"": ""\/Date(974764544000)\/"" + ""BirthDate"": ""2000-11-20T23:55:44Z"", + ""LastModified"": ""2000-11-20T23:55:44Z"" }", json); } @@ -160,7 +160,7 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(@"{ ""name"": ""Widget"", - ""expiryDate"": ""\/Date(1292868060000)\/"", + ""expiryDate"": ""2010-12-20T18:01:00Z"", ""price"": 9.99, ""sizes"": [ ""Small"", diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs index eef6b5e..178e105 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs @@ -70,7 +70,7 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(@"{ ""FirstName"": ""Maurice"", ""LastName"": ""Moss"", - ""BirthDate"": ""\/Date(252291661000)\/"" + ""BirthDate"": ""1977-12-30T01:01:01Z"" }", iPersonJson); } diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 39b3103..d568eec 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -486,12 +486,12 @@ keyword such as type of business."" string expected = sr.ReadToEnd(); - result = JsonConvert.SerializeObject(testDates); + result = JsonConvert.SerializeObject(testDates, new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat }); Assert.AreEqual(expected, result); } [Test] - public void DateTimeOffset() + public void DateTimeOffsetIso() { List testDates = new List { @@ -502,6 +502,21 @@ keyword such as type of business."" }; string result = JsonConvert.SerializeObject(testDates); + Assert.AreEqual(@"[""0100-01-01T01:01:01+00:00"",""2000-01-01T01:01:01+00:00"",""2000-01-01T01:01:01+13:00"",""2000-01-01T01:01:01-03:30""]", result); + } + + [Test] + public void DateTimeOffsetMsAjax() + { + List testDates = new List + { + new DateTimeOffset(new DateTime(100, 1, 1, 1, 1, 1, DateTimeKind.Utc)), + new DateTimeOffset(2000, 1, 1, 1, 1, 1, TimeSpan.Zero), + new DateTimeOffset(2000, 1, 1, 1, 1, 1, TimeSpan.FromHours(13)), + new DateTimeOffset(2000, 1, 1, 1, 1, 1, TimeSpan.FromHours(-3.5)), + }; + + string result = JsonConvert.SerializeObject(testDates, new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat }); Assert.AreEqual(@"[""\/Date(-59011455539000+0000)\/"",""\/Date(946688461000+0000)\/"",""\/Date(946641661000+1300)\/"",""\/Date(946701061000-0330)\/""]", result); } #endif @@ -536,7 +551,7 @@ keyword such as type of business."" }; string json = JsonConvert.SerializeObject(anonymous); - Assert.AreEqual(@"{""StringValue"":""I am a string"",""IntValue"":2147483647,""NestedAnonymous"":{""NestedValue"":255},""NestedArray"":[1,2],""Product"":{""Name"":""TestProduct"",""ExpiryDate"":""\/Date(946684800000)\/"",""Price"":0.0,""Sizes"":null}}", json); + Assert.AreEqual(@"{""StringValue"":""I am a string"",""IntValue"":2147483647,""NestedAnonymous"":{""NestedValue"":255},""NestedArray"":[1,2],""Product"":{""Name"":""TestProduct"",""ExpiryDate"":""2000-01-01T00:00:00Z"",""Price"":0.0,""Sizes"":null}}", json); anonymous = JsonConvert.DeserializeAnonymousType(json, anonymous); Assert.AreEqual("I am a string", anonymous.StringValue); @@ -565,7 +580,7 @@ keyword such as type of business."" jsonSerializer.Serialize(sw, collection); - Assert.AreEqual(@"[{""Name"":""Test1"",""ExpiryDate"":""\/Date(946684800000)\/"",""Price"":0.0,""Sizes"":null},{""Name"":""Test2"",""ExpiryDate"":""\/Date(946684800000)\/"",""Price"":0.0,""Sizes"":null},{""Name"":""Test3"",""ExpiryDate"":""\/Date(946684800000)\/"",""Price"":0.0,""Sizes"":null}]", + Assert.AreEqual(@"[{""Name"":""Test1"",""ExpiryDate"":""2000-01-01T00:00:00Z"",""Price"":0.0,""Sizes"":null},{""Name"":""Test2"",""ExpiryDate"":""2000-01-01T00:00:00Z"",""Price"":0.0,""Sizes"":null},{""Name"":""Test3"",""ExpiryDate"":""2000-01-01T00:00:00Z"",""Price"":0.0,""Sizes"":null}]", sw.GetStringBuilder().ToString()); ProductCollection collectionNew = (ProductCollection) jsonSerializer.Deserialize(new JsonTextReader(new StringReader(sw.GetStringBuilder().ToString())), typeof (ProductCollection)); @@ -819,12 +834,30 @@ keyword such as type of business."" } [Test] - public void SerializerShouldUseMemberConverter() + public void SerializerShouldUseMemberConverter_IsoDate() { DateTime testDate = new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc); MemberConverterClass m1 = new MemberConverterClass {DefaultConverter = testDate, MemberConverter = testDate}; string json = JsonConvert.SerializeObject(m1); + Assert.AreEqual(@"{""DefaultConverter"":""1970-01-01T00:00:00Z"",""MemberConverter"":""1970-01-01T00:00:00Z""}", json); + + MemberConverterClass m2 = JsonConvert.DeserializeObject(json); + + Assert.AreEqual(testDate, m2.DefaultConverter); + Assert.AreEqual(testDate, m2.MemberConverter); + } + + [Test] + public void SerializerShouldUseMemberConverter_MsDate() + { + DateTime testDate = new DateTime(JsonConvert.InitialJavaScriptDateTicks, DateTimeKind.Utc); + MemberConverterClass m1 = new MemberConverterClass { DefaultConverter = testDate, MemberConverter = testDate }; + + string json = JsonConvert.SerializeObject(m1, new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat + }); Assert.AreEqual(@"{""DefaultConverter"":""\/Date(0)\/"",""MemberConverter"":""1970-01-01T00:00:00Z""}", json); MemberConverterClass m2 = JsonConvert.DeserializeObject(json); @@ -1017,7 +1050,7 @@ keyword such as type of business."" ""FirstName"": ""Bob"", ""MiddleName"": ""Cosmo"", ""LastName"": ""Smith"", - ""BirthDate"": ""\/Date(977309755000)\/"" + ""BirthDate"": ""2000-12-20T10:55:55Z"" }", json); RequiredMembersClass c2 = JsonConvert.DeserializeObject(json); @@ -1775,13 +1808,13 @@ keyword such as type of business."" Assert.AreEqual(@"[ { ""Name"": ""Product 1"", - ""ExpiryDate"": ""\/Date(978048000000)\/"", + ""ExpiryDate"": ""2000-12-29T00:00:00Z"", ""Price"": 99.95, ""Sizes"": null }, { ""Name"": ""Product 2"", - ""ExpiryDate"": ""\/Date(1248998400000)\/"", + ""ExpiryDate"": ""2009-07-31T00:00:00Z"", ""Price"": 12.50, ""Sizes"": null } @@ -2164,8 +2197,8 @@ keyword such as type of business."" ""Person"": { ""HourlyWage"": 12.50, ""Name"": ""Jim Bob"", - ""BirthDate"": ""\/Date(975542399000)\/"", - ""LastModified"": ""\/Date(975542399000)\/"" + ""BirthDate"": ""2000-11-29T23:59:59Z"", + ""LastModified"": ""2000-11-29T23:59:59Z"" } }", json); @@ -2667,7 +2700,7 @@ keyword such as type of business."" } [Test] - public void SerializeISerializableTestObject() + public void SerializeISerializableTestObject_IsoDate() { Person person = new Person(); person.BirthDate = new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Utc); @@ -2678,7 +2711,7 @@ keyword such as type of business."" DateTimeOffset dateTimeOffset = new DateTimeOffset(2000, 12, 20, 22, 59, 59, TimeSpan.FromHours(2)); string dateTimeOffsetText; #if !NET20 - dateTimeOffsetText = @"\/Date(977345999000+0200)\/"; + dateTimeOffsetText = @"2000-12-20T22:59:59+02:00"; #else dateTimeOffsetText = @"12/20/2000 22:59:59 +02:00"; #endif @@ -2690,6 +2723,63 @@ keyword such as type of business."" ""stringValue"": ""String!"", ""intValue"": -2147483648, ""dateTimeOffsetValue"": """ + dateTimeOffsetText + @""", + ""personValue"": { + ""Name"": ""Name!"", + ""BirthDate"": ""2000-01-01T01:01:01Z"", + ""LastModified"": ""2000-01-01T01:01:01Z"" + }, + ""nullPersonValue"": null, + ""nullableInt"": null, + ""booleanValue"": false, + ""byteValue"": 0, + ""charValue"": ""\u0000"", + ""dateTimeValue"": ""0001-01-01T00:00:00Z"", + ""decimalValue"": 0.0, + ""shortValue"": 0, + ""longValue"": 0, + ""sbyteValue"": 0, + ""floatValue"": 0.0, + ""ushortValue"": 0, + ""uintValue"": 0, + ""ulongValue"": 0 +}", json); + + ISerializableTestObject o2 = JsonConvert.DeserializeObject(json); + Assert.AreEqual("String!", o2._stringValue); + Assert.AreEqual(int.MinValue, o2._intValue); + Assert.AreEqual(dateTimeOffset, o2._dateTimeOffsetValue); + Assert.AreEqual("Name!", o2._personValue.Name); + Assert.AreEqual(null, o2._nullPersonValue); + Assert.AreEqual(null, o2._nullableInt); + } + + [Test] + public void SerializeISerializableTestObject_MsAjax() + { + Person person = new Person(); + person.BirthDate = new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Utc); + person.LastModified = person.BirthDate; + person.Department = "Department!"; + person.Name = "Name!"; + + DateTimeOffset dateTimeOffset = new DateTimeOffset(2000, 12, 20, 22, 59, 59, TimeSpan.FromHours(2)); + string dateTimeOffsetText; +#if !NET20 + dateTimeOffsetText = @"\/Date(977345999000+0200)\/"; +#else + dateTimeOffsetText = @"12/20/2000 22:59:59 +02:00"; +#endif + + ISerializableTestObject o = new ISerializableTestObject("String!", int.MinValue, dateTimeOffset, person); + + string json = JsonConvert.SerializeObject(o, Formatting.Indented, new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat + }); + Assert.AreEqual(@"{ + ""stringValue"": ""String!"", + ""intValue"": -2147483648, + ""dateTimeOffsetValue"": """ + dateTimeOffsetText + @""", ""personValue"": { ""Name"": ""Name!"", ""BirthDate"": ""\/Date(946688461000)\/"", @@ -3161,8 +3251,8 @@ keyword such as type of business."" ""Value"": { ""HourlyWage"": 1.0, ""Name"": null, - ""BirthDate"": ""\/Date(975711661000)\/"", - ""LastModified"": ""\/Date(975711661000)\/"" + ""BirthDate"": ""2000-12-01T23:01:01Z"", + ""LastModified"": ""2000-12-01T23:01:01Z"" } }, { @@ -3170,8 +3260,8 @@ keyword such as type of business."" ""Value"": { ""HourlyWage"": 2.0, ""Name"": null, - ""BirthDate"": ""\/Date(975711661000)\/"", - ""LastModified"": ""\/Date(975711661000)\/"" + ""BirthDate"": ""2000-12-01T23:01:01Z"", + ""LastModified"": ""2000-12-01T23:01:01Z"" } } ]", json); @@ -3777,13 +3867,34 @@ keyword such as type of business."" #if !NET20 [Test] - public void ReadWriteTimeZoneOffset() + public void ReadWriteTimeZoneOffsetIso() { var serializeObject = JsonConvert.SerializeObject(new TimeZoneOffsetObject { Offset = new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)) }); + Assert.AreEqual("{\"Offset\":\"2000-01-01T00:00:00+06:00\"}", serializeObject); + var deserializeObject = JsonConvert.DeserializeObject(serializeObject); + Assert.AreEqual(TimeSpan.FromHours(6), deserializeObject.Offset.Offset); + Assert.AreEqual(new DateTime(2000, 1, 1), deserializeObject.Offset.Date); + } + + [Test] + public void DeserializePropertyNullableDateTimeOffsetExactIso() + { + NullableDateTimeTestClass d = JsonConvert.DeserializeObject("{\"DateTimeOffsetField\":\"2000-01-01T00:00:00+06:00\"}"); + Assert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)), d.DateTimeOffsetField); + } + + [Test] + public void ReadWriteTimeZoneOffsetMsAjax() + { + var serializeObject = JsonConvert.SerializeObject(new TimeZoneOffsetObject + { + Offset = new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)) + }, Formatting.None, new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat }); + Assert.AreEqual("{\"Offset\":\"\\/Date(946663200000+0600)\\/\"}", serializeObject); var deserializeObject = JsonConvert.DeserializeObject(serializeObject); Assert.AreEqual(TimeSpan.FromHours(6), deserializeObject.Offset.Offset); @@ -3791,7 +3902,7 @@ keyword such as type of business."" } [Test] - public void DeserializePropertyNullableDateTimeOffsetExact() + public void DeserializePropertyNullableDateTimeOffsetExactMsAjax() { NullableDateTimeTestClass d = JsonConvert.DeserializeObject("{\"DateTimeOffsetField\":\"\\/Date(946663200000+0600)\\/\"}"); Assert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(6)), d.DateTimeOffsetField); @@ -3839,7 +3950,7 @@ keyword such as type of business."" ""Decimal"": 99.9, ""Complex"": { ""String"": ""I am a string"", - ""DateTime"": ""\/Date(977338500000)\/"" + ""DateTime"": ""2000-12-20T18:55:00Z"" } }", json); @@ -5168,7 +5279,7 @@ keyword such as type of business."" } [Test] - [ExpectedException(typeof (JsonReaderException), ExpectedMessage = "Unexpected token when reading integer: Boolean. Line 2, position 22.")] + [ExpectedException(typeof(JsonReaderException), ExpectedMessage = "Error reading integer. Unexpected token: Boolean. Line 2, position 22.")] public void DeserializeBoolInt() { string json = @"{ @@ -5223,7 +5334,7 @@ keyword such as type of business."" ""BrokerID"": ""951663c4-924e-4c86-a57a-7ed737501dbd"", ""Latitude"": 33.657145, ""Longitude"": -117.766684, - ""TimeStamp"": ""\/Date(951955199000)\/"", + ""TimeStamp"": ""2000-03-01T23:59:59Z"", ""Payload"": { ""$type"": ""System.Byte[], mscorlib"", ""$value"": ""AAECAwQFBgcICQ=="" @@ -5372,6 +5483,79 @@ Parameter name: value")] { JsonConvert.DeserializeObject(null); } + + [Test] + public void DeserializeIsoDatesWithIsoConverter() + { + string jsonIsoText = + @"{""Value"":""2012-02-25T19:55:50.6095676+13:00""}"; + + DateTimeWrapper c = JsonConvert.DeserializeObject(jsonIsoText, new IsoDateTimeConverter()); + Assert.AreEqual(DateTimeKind.Local, c.Value.Kind); + } + +#if !NET20 + [Test] + public void DeserializeUTC() + { + DateTimeTestClass c = + JsonConvert.DeserializeObject( + @"{""PreField"":""Pre"",""DateTimeField"":""2008-12-12T12:12:12Z"",""DateTimeOffsetField"":""2008-12-12T12:12:12Z"",""PostField"":""Post""}", + new JsonSerializerSettings + { + DateTimeZoneHandling = DateTimeZoneHandling.Local + }); + + Assert.AreEqual(new DateTime(2008, 12, 12, 12, 12, 12, 0, DateTimeKind.Utc).ToLocalTime(), c.DateTimeField); + Assert.AreEqual(new DateTimeOffset(2008, 12, 12, 12, 12, 12, 0, TimeSpan.Zero), c.DateTimeOffsetField); + Assert.AreEqual("Pre", c.PreField); + Assert.AreEqual("Post", c.PostField); + + DateTimeTestClass c2 = + JsonConvert.DeserializeObject( + @"{""PreField"":""Pre"",""DateTimeField"":""2008-01-01T01:01:01Z"",""DateTimeOffsetField"":""2008-01-01T01:01:01Z"",""PostField"":""Post""}", + new JsonSerializerSettings + { + DateTimeZoneHandling = DateTimeZoneHandling.Local + }); + + Assert.AreEqual(new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Utc).ToLocalTime(), c2.DateTimeField); + Assert.AreEqual(new DateTimeOffset(2008, 1, 1, 1, 1, 1, 0, TimeSpan.Zero), c2.DateTimeOffsetField); + Assert.AreEqual("Pre", c2.PreField); + Assert.AreEqual("Post", c2.PostField); + } + + [Test] + public void NullableDeserializeUTC() + { + NullableDateTimeTestClass c = + JsonConvert.DeserializeObject( + @"{""PreField"":""Pre"",""DateTimeField"":""2008-12-12T12:12:12Z"",""DateTimeOffsetField"":""2008-12-12T12:12:12Z"",""PostField"":""Post""}", + new JsonSerializerSettings + { + DateTimeZoneHandling = DateTimeZoneHandling.Local + }); + + Assert.AreEqual(new DateTime(2008, 12, 12, 12, 12, 12, 0, DateTimeKind.Utc).ToLocalTime(), c.DateTimeField); + Assert.AreEqual(new DateTimeOffset(2008, 12, 12, 12, 12, 12, 0, TimeSpan.Zero), c.DateTimeOffsetField); + Assert.AreEqual("Pre", c.PreField); + Assert.AreEqual("Post", c.PostField); + + NullableDateTimeTestClass c2 = + JsonConvert.DeserializeObject( + @"{""PreField"":""Pre"",""DateTimeField"":null,""DateTimeOffsetField"":null,""PostField"":""Post""}"); + + Assert.AreEqual(null, c2.DateTimeField); + Assert.AreEqual(null, c2.DateTimeOffsetField); + Assert.AreEqual("Pre", c2.PreField); + Assert.AreEqual("Post", c2.PostField); + } +#endif + } + + public class DateTimeWrapper + { + public DateTime Value { get; set; } } public class Widget1 diff --git a/Src/Newtonsoft.Json.Tests/Serialization/NullValueHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/NullValueHandlingTests.cs index 2c87e71..21ed6a5 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/NullValueHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/NullValueHandlingTests.cs @@ -62,7 +62,7 @@ namespace Newtonsoft.Json.Tests.Serialization //JsonConvert.ConvertDateTimeToJavaScriptTicks(s1.Establised.DateTime) - Assert.AreEqual(@"{""Color"":4,""Establised"":""\/Date(1264122061000)\/"",""Width"":1.1,""Employees"":999,""RoomsPerFloor"":[1,2,3,4,5,6,7,8,9],""Open"":false,""Symbol"":""@"",""Mottos"":[""Hello World"",""öäüÖÄÜ\\'{new Date(12345);}[222]_µ@²³~"",null,"" ""],""Cost"":100980.1,""Escape"":""\r\n\t\f\b?{\\r\\n\""'"",""product"":[{""Name"":""Rocket"",""ExpiryDate"":""\/Date(949532490000)\/"",""Price"":0.0},{""Name"":""Alien"",""ExpiryDate"":""\/Date(946684800000)\/"",""Price"":0.0}]}", sw.GetStringBuilder().ToString()); + Assert.AreEqual(@"{""Color"":4,""Establised"":""2010-01-22T01:01:01Z"",""Width"":1.1,""Employees"":999,""RoomsPerFloor"":[1,2,3,4,5,6,7,8,9],""Open"":false,""Symbol"":""@"",""Mottos"":[""Hello World"",""öäüÖÄÜ\\'{new Date(12345);}[222]_µ@²³~"",null,"" ""],""Cost"":100980.1,""Escape"":""\r\n\t\f\b?{\\r\\n\""'"",""product"":[{""Name"":""Rocket"",""ExpiryDate"":""2000-02-02T23:01:30Z"",""Price"":0.0},{""Name"":""Alien"",""ExpiryDate"":""2000-01-01T00:00:00Z"",""Price"":0.0}]}", sw.GetStringBuilder().ToString()); Store s2 = (Store)jsonSerializer.Deserialize(new JsonTextReader(new StringReader("{}")), typeof(Store)); Assert.AreEqual("\r\n\t\f\b?{\\r\\n\"\'", s2.Escape); diff --git a/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs index a6a4ec5..6532f96 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs @@ -290,10 +290,10 @@ namespace Newtonsoft.Json.Tests.Serialization e = ex; } - Assert.AreEqual(@"Error converting value ""kjhkjhkjhkjh"" to type 'System.DateTime'. Line 1, position 16.", e.Message); + Assert.AreEqual(@"Could not convert string to DateTime: kjhkjhkjhkjh. Line 1, position 16.", e.Message); Assert.AreEqual(1, errors.Count); - Assert.AreEqual(@"[0][0] - 0 - Error converting value ""kjhkjhkjhkjh"" to type 'System.DateTime'. Line 1, position 16.", errors[0]); + Assert.AreEqual(@"[0][0] - 0 - Could not convert string to DateTime: kjhkjhkjhkjh. Line 1, position 16.", errors[0]); } [Test] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs index 9d52d82..48af72c 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs @@ -192,8 +192,8 @@ namespace Newtonsoft.Json.Tests.Serialization { ""$type"": """ + personRef + @""", ""Name"": null, - ""BirthDate"": ""\/Date(978134400000)\/"", - ""LastModified"": ""\/Date(978134400000)\/"" + ""BirthDate"": ""2000-12-30T00:00:00Z"", + ""LastModified"": ""2000-12-30T00:00:00Z"" }, ""String!"", -2147483648 @@ -870,7 +870,7 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(output, @"{ ""$type"": """ + carClassRef + @""", - ""Year"": ""\/Date(970707661000)\/"", + ""Year"": ""2000-10-05T01:01:01Z"", ""Objects"": { ""$type"": ""System.Object[], mscorlib"", ""$values"": [ diff --git a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs index 7560f02..0a47016 100644 --- a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs +++ b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs @@ -26,10 +26,12 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Threading; using NUnit.Framework; +using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Tests { @@ -53,5 +55,14 @@ namespace Newtonsoft.Json.Tests { return @"@""" + json.Replace(@"""", @"""""") + @""""; } + + protected string GetOffset(DateTime d, DateFormatHandling dateFormatHandling) + { + StringWriter sw = new StringWriter(); + JsonConvert.WriteDateTimeOffset(sw, DateTime.SpecifyKind(d, DateTimeKind.Local).GetUtcOffset(), dateFormatHandling); + sw.Flush(); + + return sw.ToString(); + } } } diff --git a/Src/Newtonsoft.Json/Bson/BsonReader.cs b/Src/Newtonsoft.Json/Bson/BsonReader.cs index c80491d..099e561 100644 --- a/Src/Newtonsoft.Json/Bson/BsonReader.cs +++ b/Src/Newtonsoft.Json/Bson/BsonReader.cs @@ -180,49 +180,7 @@ namespace Newtonsoft.Json.Bson /// public override byte[] ReadAsBytes() { - Read(); - - if (IsWrappedInTypeObject()) - { - byte[] data = ReadAsBytes(); - Read(); - SetToken(JsonToken.Bytes, data); - return data; - } - - if (TokenType == JsonToken.Null) - return null; - if (TokenType == JsonToken.Bytes) - return (byte[])Value; - - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); - } - - private bool IsWrappedInTypeObject() - { - if (TokenType == JsonToken.StartObject) - { - Read(); - if (Value.ToString() == "$type") - { - Read(); - if (Value != null && Value.ToString().StartsWith("System.Byte[]")) - { - Read(); - if (Value.ToString() == "$value") - { - return true; - } - } - } - - throw CreateReaderException(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, JsonToken.StartObject)); - } - - return false; + return ReadAsBytesInternal(); } /// @@ -231,35 +189,7 @@ namespace Newtonsoft.Json.Bson /// A . This method will return null at the end of an array. public override decimal? ReadAsDecimal() { - Read(); - - if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) - { - SetToken(JsonToken.Float, Convert.ToDecimal(Value, CultureInfo.InvariantCulture)); - return (decimal)Value; - } - - if (TokenType == JsonToken.Null) - return null; - - decimal d; - if (TokenType == JsonToken.String) - { - if (decimal.TryParse((string)Value, NumberStyles.Number, Culture, out d)) - { - SetToken(JsonToken.Float, d); - return d; - } - else - { - throw CreateReaderException(this, "Could not convert string to decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } - - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Error reading decimal. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + return ReadAsDecimalInternal(); } /// @@ -268,35 +198,25 @@ namespace Newtonsoft.Json.Bson /// A . This method will return null at the end of an array. public override int? ReadAsInt32() { - Read(); - - if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) - { - SetToken(JsonToken.Float, Convert.ToInt32(Value, CultureInfo.InvariantCulture)); - return (int)Value; - } - - if (TokenType == JsonToken.Null) - return null; - - int i; - if (TokenType == JsonToken.String) - { - if (int.TryParse((string)Value, NumberStyles.Integer, Culture, out i)) - { - SetToken(JsonToken.Integer, i); - return i; - } - else - { - throw CreateReaderException(this, "Could not convert string to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } + return ReadAsInt32Internal(); + } - if (TokenType == JsonToken.EndArray) - return null; + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override string ReadAsString() + { + return ReadAsStringInternal(); + } - throw CreateReaderException(this, "Error reading integer. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override DateTime? ReadAsDateTime() + { + return ReadAsDateTimeInternal(); } #if !NET20 @@ -308,35 +228,7 @@ namespace Newtonsoft.Json.Bson /// public override DateTimeOffset? ReadAsDateTimeOffset() { - Read(); - - if (TokenType == JsonToken.Date) - { - SetToken(JsonToken.Date, new DateTimeOffset((DateTime) Value)); - return (DateTimeOffset) Value; - } - - if (TokenType == JsonToken.Null) - return null; - - DateTimeOffset dt; - if (TokenType == JsonToken.String) - { - if (DateTimeOffset.TryParse((string)Value, Culture, DateTimeStyles.None, out dt)) - { - SetToken(JsonToken.Date, dt); - return dt; - } - else - { - throw CreateReaderException(this, "Could not convert string to DateTimeOffset: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } - - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Error reading date. Expected date but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + return ReadAsDateTimeOffsetInternal(); } #endif @@ -347,6 +239,13 @@ namespace Newtonsoft.Json.Bson /// true if the next token was read successfully; false if there are no more tokens to read. /// public override bool Read() + { + _readType = Json.ReadType.Read; + + return ReadInternal(); + } + + internal override bool ReadInternal() { try { diff --git a/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs index 2a2dac8..04004b1 100644 --- a/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs @@ -107,6 +107,16 @@ namespace Newtonsoft.Json.Converters return null; } + if (reader.TokenType == JsonToken.Date) + { +#if !PocketPC && !NET20 + if (t == typeof(DateTimeOffset)) + return new DateTimeOffset((DateTime)reader.Value); +#endif + + return reader.Value; + } + if (reader.TokenType != JsonToken.String) throw new Exception("Unexpected token parsing date. Expected String, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType)); diff --git a/Src/Newtonsoft.Json/Converters/JsonDateTimeSerializationMode.cs b/Src/Newtonsoft.Json/Converters/JsonDateTimeSerializationMode.cs deleted file mode 100644 index 089b89d..0000000 --- a/Src/Newtonsoft.Json/Converters/JsonDateTimeSerializationMode.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Newtonsoft.Json.Converters -{ - /// - /// Specifies whether a DateTime object represents a local time, a Coordinated Universal Time (UTC), or is not specified as either local time or UTC. - /// - public enum JsonDateTimeSerializationMode - { - /// - /// The time represented is local time. - /// - Local, - /// - /// The time represented is UTC. - /// - Utc, - /// - /// The time represented is not specified as either local time or Coordinated Universal Time (UTC). - /// - Unspecified, - /// - /// Preserves the DateTimeKind field of a date when a DateTime object is converted to a string and the string is then converted back to a DateTime object. - /// - RoundtripKind - } -} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/DateFormatHandling.cs b/Src/Newtonsoft.Json/DateFormatHandling.cs new file mode 100644 index 0000000..55902c7 --- /dev/null +++ b/Src/Newtonsoft.Json/DateFormatHandling.cs @@ -0,0 +1,8 @@ +namespace Newtonsoft.Json +{ + public enum DateFormatHandling + { + IsoDateFormat, + MicrosoftDateFormat + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/DateTimeZoneHandling.cs b/Src/Newtonsoft.Json/DateTimeZoneHandling.cs new file mode 100644 index 0000000..9900d23 --- /dev/null +++ b/Src/Newtonsoft.Json/DateTimeZoneHandling.cs @@ -0,0 +1,28 @@ +using System; + +namespace Newtonsoft.Json +{ + /// + /// Specifies how to treat the time value when converting between string and . + /// + public enum DateTimeZoneHandling + { + /// + /// Treat as local time. If the object represents a Coordinated Universal Time (UTC), it is converted to the local time. + /// + Local, + /// + /// Treat as a UTC. If the object represents a local time, it is converted to a UTC. + /// + Utc, + /// + /// Treat as a local time if a is being converted to a string. + /// If a string is being converted to , convert to a local time if a time zone is specified. + /// + Unspecified, + /// + /// Time zone information should be preserved when converting. + /// + RoundtripKind + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Formatting.cs b/Src/Newtonsoft.Json/Formatting.cs new file mode 100644 index 0000000..1c1de0e --- /dev/null +++ b/Src/Newtonsoft.Json/Formatting.cs @@ -0,0 +1,17 @@ +namespace Newtonsoft.Json +{ + /// + /// Specifies formatting options for the . + /// + public enum Formatting + { + /// + /// No special formatting is applied. This is the default. + /// + None, + /// + /// Causes child objects to be indented according to the and settings. + /// + Indented + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonConvert.cs b/Src/Newtonsoft.Json/JsonConvert.cs index fc598bb..210db6c 100644 --- a/Src/Newtonsoft.Json/JsonConvert.cs +++ b/Src/Newtonsoft.Json/JsonConvert.cs @@ -85,13 +85,48 @@ namespace Newtonsoft.Json /// A JSON string representation of the . public static string ToString(DateTime value) { + return ToString(value, DateFormatHandling.IsoDateFormat, DateTimeZoneHandling.RoundtripKind); + } + + /// + /// Converts the to its JSON string representation using the specified. + /// + /// The value to convert. + /// The format the date will be converted to. + /// A JSON string representation of the . + public static string ToString(DateTime value, DateFormatHandling format, DateTimeZoneHandling timeZoneOption) + { + DateTime updatedDateTime = EnsureDateTime(value, timeZoneOption); + using (StringWriter writer = StringUtils.CreateStringWriter(64)) { - WriteDateTimeString(writer, value, value.GetUtcOffset(), value.Kind); + WriteDateTimeString(writer, updatedDateTime, updatedDateTime.GetUtcOffset(), updatedDateTime.Kind, format); return writer.ToString(); } } + internal static DateTime EnsureDateTime(DateTime value, DateTimeZoneHandling timeZone) + { + switch (timeZone) + { + case DateTimeZoneHandling.Local: + value = SwitchToLocalTime(value); + break; + case DateTimeZoneHandling.Utc: + value = SwitchToUtcTime(value); + break; + case DateTimeZoneHandling.Unspecified: + value = new DateTime(value.Ticks, DateTimeKind.Unspecified); + break; + case DateTimeZoneHandling.RoundtripKind: + break; + default: + throw new ArgumentException("Invalid date time handling value."); + } + + return value; + } + #if !PocketPC && !NET20 /// /// Converts the to its JSON string representation. @@ -99,45 +134,89 @@ namespace Newtonsoft.Json /// The value to convert. /// A JSON string representation of the . public static string ToString(DateTimeOffset value) + { + return ToString(value, DateFormatHandling.IsoDateFormat); + } + + /// + /// Converts the to its JSON string representation using the specified. + /// + /// The value to convert. + /// The format the date will be converted to. + /// A JSON string representation of the . + public static string ToString(DateTimeOffset value, DateFormatHandling format) { using (StringWriter writer = StringUtils.CreateStringWriter(64)) { - WriteDateTimeString(writer, value.UtcDateTime, value.Offset, DateTimeKind.Local); + WriteDateTimeString(writer, (format == DateFormatHandling.IsoDateFormat) ? value.DateTime : value.UtcDateTime, value.Offset, DateTimeKind.Local, format); return writer.ToString(); } } #endif - internal static void WriteDateTimeString(TextWriter writer, DateTime value) + internal static void WriteDateTimeString(TextWriter writer, DateTime value, DateFormatHandling format) { - WriteDateTimeString(writer, value, value.GetUtcOffset(), value.Kind); + WriteDateTimeString(writer, value, value.GetUtcOffset(), value.Kind, format); } - internal static void WriteDateTimeString(TextWriter writer, DateTime value, TimeSpan offset, DateTimeKind kind) + internal static void WriteDateTimeString(TextWriter writer, DateTime value, TimeSpan offset, DateTimeKind kind, DateFormatHandling format) { - long javaScriptTicks = ConvertDateTimeToJavaScriptTicks(value, offset); + if (format == DateFormatHandling.MicrosoftDateFormat) + { + long javaScriptTicks = ConvertDateTimeToJavaScriptTicks(value, offset); - writer.Write(@"""\/Date("); - writer.Write(javaScriptTicks); + writer.Write(@"""\/Date("); + writer.Write(javaScriptTicks); - switch (kind) + switch (kind) + { + case DateTimeKind.Unspecified: + if (value != DateTime.MaxValue && value != DateTime.MinValue) + WriteDateTimeOffset(writer, offset, format); + break; + case DateTimeKind.Local: + WriteDateTimeOffset(writer, offset, format); + break; + } + + writer.Write(@")\/"""); + } + else { - case DateTimeKind.Local: - case DateTimeKind.Unspecified: - writer.Write((offset.Ticks >= 0L) ? "+" : "-"); - - int absHours = Math.Abs(offset.Hours); - if (absHours < 10) - writer.Write(0); - writer.Write(absHours); - int absMinutes = Math.Abs(offset.Minutes); - if (absMinutes < 10) - writer.Write(0); - writer.Write(absMinutes); - break; + writer.Write(@""""); + writer.Write(value.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFF", CultureInfo.InvariantCulture)); + + switch (kind) + { + case DateTimeKind.Local: + WriteDateTimeOffset(writer, offset, format); + break; + case DateTimeKind.Utc: + writer.Write("Z"); + break; + } + + + writer.Write(@""""); } + } + + internal static void WriteDateTimeOffset(TextWriter writer, TimeSpan offset, DateFormatHandling format) + { + writer.Write((offset.Ticks >= 0L) ? "+" : "-"); + + int absHours = Math.Abs(offset.Hours); + if (absHours < 10) + writer.Write(0); + writer.Write(absHours); - writer.Write(@")\/"""); + if (format == DateFormatHandling.IsoDateFormat) + writer.Write(':'); + + int absMinutes = Math.Abs(offset.Minutes); + if (absMinutes < 10) + writer.Write(0); + writer.Write(absMinutes); } private static long ToUniversalTicks(DateTime dateTime) @@ -150,7 +229,9 @@ namespace Newtonsoft.Json private static long ToUniversalTicks(DateTime dateTime, TimeSpan offset) { - if (dateTime.Kind == DateTimeKind.Utc) + // special case min and max value + // they never have a timezone appended to avoid issues + if (dateTime.Kind == DateTimeKind.Utc || dateTime == DateTime.MaxValue || dateTime == DateTime.MinValue) return dateTime.Ticks; long ticks = dateTime.Ticks - offset.Ticks; @@ -196,6 +277,38 @@ namespace Newtonsoft.Json return dateTime; } + private static DateTime SwitchToLocalTime(DateTime value) + { + switch (value.Kind) + { + case DateTimeKind.Unspecified: + return new DateTime(value.Ticks, DateTimeKind.Local); + + case DateTimeKind.Utc: + return value.ToLocalTime(); + + case DateTimeKind.Local: + return value; + } + return value; + } + + private static DateTime SwitchToUtcTime(DateTime value) + { + switch (value.Kind) + { + case DateTimeKind.Unspecified: + return new DateTime(value.Ticks, DateTimeKind.Utc); + + case DateTimeKind.Utc: + return value; + + case DateTimeKind.Local: + return value.ToUniversalTime(); + } + return value; + } + /// /// Converts the to its JSON string representation. /// @@ -579,6 +692,20 @@ namespace Newtonsoft.Json return SerializeObject(value, formatting, settings); } + /// + /// Serializes the specified object to a JSON string using a collection of . + /// + /// The object to serialize. + /// The used to serialize the object. + /// If this is null, default serialization settings will be is used. + /// + /// A JSON string representation of the object. + /// + public static string SerializeObject(object value, JsonSerializerSettings settings) + { + return SerializeObject(value, Formatting.None, settings); + } + /// /// Serializes the specified object to a JSON string using a collection of . /// diff --git a/Src/Newtonsoft.Json/JsonPosition.cs b/Src/Newtonsoft.Json/JsonPosition.cs new file mode 100644 index 0000000..c1ecfed --- /dev/null +++ b/Src/Newtonsoft.Json/JsonPosition.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Text; + +namespace Newtonsoft.Json +{ + internal enum JsonContainerType + { + None, + Object, + Array, + Constructor + } + + internal struct JsonPosition + { + internal JsonContainerType Type; + internal int? Position; + internal string PropertyName; + + internal void WriteTo(StringBuilder sb) + { + switch (Type) + { + case JsonContainerType.Object: + if (PropertyName != null) + { + if (sb.Length > 0) + sb.Append("."); + sb.Append(PropertyName); + } + break; + case JsonContainerType.Array: + case JsonContainerType.Constructor: + if (Position != null) + { + sb.Append("["); + sb.Append(Position); + sb.Append("]"); + } + break; + } + } + + internal bool InsideContainer() + { + switch (Type) + { + case JsonContainerType.Object: + return (PropertyName != null); + case JsonContainerType.Array: + case JsonContainerType.Constructor: + return (Position != null); + } + + return false; + } + + internal static string BuildPath(IEnumerable positions) + { + StringBuilder sb = new StringBuilder(); + + foreach (JsonPosition state in positions) + { + state.WriteTo(sb); + } + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index 691ac82..d982bd7 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -28,77 +28,11 @@ using System.Collections.Generic; using System.IO; using System.Globalization; using System.Linq; -using System.Text; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json { - internal enum JsonContainerType - { - None, - Object, - Array, - Constructor - } - - internal struct JsonPosition - { - internal JsonContainerType Type; - internal int? Position; - internal string PropertyName; - - internal void WriteTo(StringBuilder sb) - { - switch (Type) - { - case JsonContainerType.Object: - if (PropertyName != null) - { - if (sb.Length > 0) - sb.Append("."); - sb.Append(PropertyName); - } - break; - case JsonContainerType.Array: - case JsonContainerType.Constructor: - if (Position != null) - { - sb.Append("["); - sb.Append(Position); - sb.Append("]"); - } - break; - } - } - - internal bool InsideContainer() - { - switch (Type) - { - case JsonContainerType.Object: - return (PropertyName != null); - case JsonContainerType.Array: - case JsonContainerType.Constructor: - return (Position != null); - } - - return false; - } - - internal static string BuildPath(IEnumerable positions) - { - StringBuilder sb = new StringBuilder(); - - foreach (JsonPosition state in positions) - { - state.WriteTo(sb); - } - - return sb.ToString(); - } - } - /// /// Represents a reader that provides fast, non-cached, forward-only access to serialized Json data. /// @@ -168,8 +102,10 @@ namespace Newtonsoft.Json private object _value; private char _quoteChar; internal State _currentState; + internal ReadType _readType; private JsonPosition _currentPosition; private CultureInfo _culture; + private DateTimeZoneHandling _dateTimeZoneHandling; /// /// Gets the current reader state. @@ -201,6 +137,12 @@ namespace Newtonsoft.Json protected internal set { _quoteChar = value; } } + public DateTimeZoneHandling DateTimeZoneHandling + { + get { return _dateTimeZoneHandling; } + set { _dateTimeZoneHandling = value; } + } + /// /// Gets the type of the current JSON token. /// @@ -271,6 +213,7 @@ namespace Newtonsoft.Json { _currentState = State.Start; _stack = new List(4); + _dateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; CloseInput = true; } @@ -329,6 +272,12 @@ namespace Newtonsoft.Json /// A . This method will return null at the end of an array. public abstract int? ReadAsInt32(); + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public abstract string ReadAsString(); + /// /// Reads the next JSON token from the stream as a . /// @@ -341,6 +290,12 @@ namespace Newtonsoft.Json /// A . This method will return null at the end of an array. public abstract decimal? ReadAsDecimal(); + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public abstract DateTime? ReadAsDateTime(); + #if !NET20 /// /// Reads the next JSON token from the stream as a . @@ -349,6 +304,329 @@ namespace Newtonsoft.Json public abstract DateTimeOffset? ReadAsDateTimeOffset(); #endif + internal virtual bool ReadInternal() + { + throw new NotImplementedException(); + } + +#if !NET20 + internal DateTimeOffset? ReadAsDateTimeOffsetInternal() + { + _readType = ReadType.ReadAsDateTimeOffset; + + do + { + if (!ReadInternal()) + { + SetToken(JsonToken.None); + return null; + } + } while (TokenType == JsonToken.Comment); + + if (TokenType == JsonToken.Date) + { + if (Value is DateTime) + SetToken(JsonToken.Date, new DateTimeOffset((DateTime)Value)); + + return (DateTimeOffset)Value; + } + + if (TokenType == JsonToken.Null) + return null; + + DateTimeOffset dt; + if (TokenType == JsonToken.String) + { + if (DateTimeOffset.TryParse((string)Value, Culture, DateTimeStyles.RoundtripKind, out dt)) + { + SetToken(JsonToken.Date, dt); + return dt; + } + else + { + throw CreateReaderException(this, "Could not convert string to DateTimeOffset: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); + } + } + + if (TokenType == JsonToken.EndArray) + return null; + + throw CreateReaderException(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + } +#endif + + internal byte[] ReadAsBytesInternal() + { + _readType = ReadType.ReadAsBytes; + + do + { + if (!ReadInternal()) + { + SetToken(JsonToken.None); + return null; + } + } while (TokenType == JsonToken.Comment); + + if (IsWrappedInTypeObject()) + { + byte[] data = ReadAsBytes(); + ReadInternal(); + SetToken(JsonToken.Bytes, data); + return data; + } + + // attempt to convert possible base 64 string to bytes + if (TokenType == JsonToken.String) + { + string s = (string)Value; + byte[] data = (s.Length == 0) ? new byte[0] : Convert.FromBase64String(s); + SetToken(JsonToken.Bytes, data); + } + + if (TokenType == JsonToken.Null) + return null; + + if (TokenType == JsonToken.Bytes) + return (byte[])Value; + + if (TokenType == JsonToken.StartArray) + { + List data = new List(); + + while (ReadInternal()) + { + switch (TokenType) + { + case JsonToken.Integer: + data.Add(Convert.ToByte(Value, CultureInfo.InvariantCulture)); + break; + case JsonToken.EndArray: + byte[] d = data.ToArray(); + SetToken(JsonToken.Bytes, d); + return d; + case JsonToken.Comment: + // skip + break; + default: + throw CreateReaderException(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + } + } + + throw CreateReaderException(this, "Unexpected end when reading bytes."); + } + + if (TokenType == JsonToken.EndArray) + return null; + + throw CreateReaderException(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + } + + internal decimal? ReadAsDecimalInternal() + { + _readType = ReadType.ReadAsDecimal; + + do + { + if (!ReadInternal()) + { + SetToken(JsonToken.None); + return null; + } + } while (TokenType == JsonToken.Comment); + + if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) + { + if (!(Value is decimal)) + SetToken(JsonToken.Float, Convert.ToDecimal(Value, CultureInfo.InvariantCulture)); + + return (decimal)Value; + } + + if (TokenType == JsonToken.Null) + return null; + + decimal d; + if (TokenType == JsonToken.String) + { + if (decimal.TryParse((string)Value, NumberStyles.Number, Culture, out d)) + { + SetToken(JsonToken.Float, d); + return d; + } + else + { + throw CreateReaderException(this, "Could not convert string to decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); + } + } + + if (TokenType == JsonToken.EndArray) + return null; + + throw CreateReaderException(this, "Error reading decimal. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + } + + internal int? ReadAsInt32Internal() + { + _readType = ReadType.ReadAsInt32; + + do + { + if (!ReadInternal()) + { + SetToken(JsonToken.None); + return null; + } + } while (TokenType == JsonToken.Comment); + + if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) + { + if (!(Value is int)) + SetToken(JsonToken.Integer, Convert.ToInt32(Value, CultureInfo.InvariantCulture)); + + return (int)Value; + } + + if (TokenType == JsonToken.Null) + return null; + + int i; + if (TokenType == JsonToken.String) + { + if (int.TryParse((string)Value, NumberStyles.Integer, Culture, out i)) + { + SetToken(JsonToken.Integer, i); + return i; + } + else + { + throw CreateReaderException(this, "Could not convert string to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); + } + } + + if (TokenType == JsonToken.EndArray) + return null; + + throw CreateReaderException(this, "Error reading integer. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + } + + internal string ReadAsStringInternal() + { + _readType = ReadType.ReadAsString; + + do + { + if (!ReadInternal()) + { + SetToken(JsonToken.None); + return null; + } + } while (TokenType == JsonToken.Comment); + + if (TokenType == JsonToken.String) + return (string)Value; + + if (TokenType == JsonToken.Null) + return null; + + if (IsPrimitiveToken(TokenType)) + { + if (Value != null) + { + string s; + if (Value is IConvertible) + s = ((IConvertible)Value).ToString(Culture); + else if (Value is IFormattable) + s = ((IFormattable)Value).ToString(null, Culture); + else + s = Value.ToString(); + + SetToken(JsonToken.String, s); + return s; + } + } + + if (TokenType == JsonToken.EndArray) + return null; + + throw CreateReaderException(this, "Error reading string. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + } + + internal DateTime? ReadAsDateTimeInternal() + { + _readType = ReadType.ReadAsDateTime; + + do + { + if (!ReadInternal()) + { + SetToken(JsonToken.None); + return null; + } + } while (TokenType == JsonToken.Comment); + + if (TokenType == JsonToken.Date) + return (DateTime)Value; + + if (TokenType == JsonToken.Null) + return null; + + DateTime dt; + if (TokenType == JsonToken.String) + { + string s = (string)Value; + if (string.IsNullOrEmpty(s)) + { + SetToken(JsonToken.Null); + return null; + } + + if (DateTime.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt)) + { + dt = JsonConvert.EnsureDateTime(dt, DateTimeZoneHandling); + SetToken(JsonToken.Date, dt); + return dt; + } + else + { + throw CreateReaderException(this, "Could not convert string to DateTime: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); + } + } + + if (TokenType == JsonToken.EndArray) + return null; + + throw CreateReaderException(this, "Error reading date. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + } + + private bool IsWrappedInTypeObject() + { + _readType = ReadType.Read; + + if (TokenType == JsonToken.StartObject) + { + if (!ReadInternal()) + throw CreateReaderException(this, "Unexpected end when reading bytes."); + + if (Value.ToString() == "$type") + { + ReadInternal(); + if (Value != null && Value.ToString().StartsWith("System.Byte[]")) + { + ReadInternal(); + if (Value.ToString() == "$value") + { + return true; + } + } + } + + throw CreateReaderException(this, "Error reading bytes. Unexpected token: {0}.".FormatWith(CultureInfo.InvariantCulture, JsonToken.StartObject)); + } + + return false; + } + /// /// Skips the children of the current token. /// diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index fbbc33a..4f01433 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Runtime.Serialization.Formatters; using Newtonsoft.Json.Converters; @@ -56,6 +57,10 @@ namespace Newtonsoft.Json private IReferenceResolver _referenceResolver; private SerializationBinder _binder; private StreamingContext _context; + private Formatting? _formatting; + private DateFormatHandling? _dateFormatHandling; + private DateTimeZoneHandling? _dateTimeZoneHandling; + private CultureInfo _culture; /// /// Occurs when the errors during serialization and deserialization. @@ -279,6 +284,30 @@ namespace Newtonsoft.Json get { return _context; } set { _context = value; } } + + public virtual Formatting Formatting + { + get { return _formatting ?? JsonSerializerSettings.DefaultFormatting; } + set { _formatting = value; } + } + + public virtual DateFormatHandling DateFormatHandling + { + get { return _dateFormatHandling ?? JsonSerializerSettings.DefaultDateFormatHandling; } + set { _dateFormatHandling = value; } + } + + public virtual DateTimeZoneHandling DateTimeZoneHandling + { + get { return _dateTimeZoneHandling ?? JsonSerializerSettings.DefaultDateTimeZoneHandling; } + set { _dateTimeZoneHandling = value; } + } + + public virtual CultureInfo Culture + { + get { return _culture ?? JsonSerializerSettings.DefaultCulture; } + set { _culture = value; } + } #endregion /// @@ -295,7 +324,6 @@ namespace Newtonsoft.Json _constructorHandling = JsonSerializerSettings.DefaultConstructorHandling; _typeNameHandling = JsonSerializerSettings.DefaultTypeNameHandling; _context = JsonSerializerSettings.DefaultContext; - _binder = DefaultSerializationBinder.Instance; } @@ -313,6 +341,7 @@ namespace Newtonsoft.Json if (!CollectionUtils.IsNullOrEmpty(settings.Converters)) jsonSerializer.Converters.AddRange(settings.Converters); + // serializer specific jsonSerializer.TypeNameHandling = settings.TypeNameHandling; jsonSerializer.TypeNameAssemblyFormat = settings.TypeNameAssemblyFormat; jsonSerializer.PreserveReferencesHandling = settings.PreserveReferencesHandling; @@ -324,6 +353,13 @@ namespace Newtonsoft.Json jsonSerializer.ConstructorHandling = settings.ConstructorHandling; jsonSerializer.Context = settings.Context; + // reader specific + // unset values won't override reader set values + jsonSerializer._formatting = settings._formatting; + jsonSerializer._dateFormatHandling = settings._dateFormatHandling; + jsonSerializer._dateTimeZoneHandling = settings._dateTimeZoneHandling; + jsonSerializer._culture = settings._culture; + if (settings.Error != null) jsonSerializer.Error += settings.Error; @@ -417,8 +453,30 @@ namespace Newtonsoft.Json { ValidationUtils.ArgumentNotNull(reader, "reader"); + // set serialization options onto reader + CultureInfo previousCulture = null; + if (_culture != null && reader.Culture != _culture) + { + previousCulture = reader.Culture; + reader.Culture = _culture; + } + DateTimeZoneHandling? previousDateTimeZoneHandling = null; + if (_dateTimeZoneHandling != null && reader.DateTimeZoneHandling != _dateTimeZoneHandling) + { + previousDateTimeZoneHandling = reader.DateTimeZoneHandling; + reader.DateTimeZoneHandling = _dateTimeZoneHandling.Value; + } + JsonSerializerInternalReader serializerReader = new JsonSerializerInternalReader(this); - return serializerReader.Deserialize(reader, objectType); + object value = serializerReader.Deserialize(reader, objectType); + + // reset reader back to previous options + if (previousCulture != null) + reader.Culture = previousCulture; + if (previousDateTimeZoneHandling != null) + reader.DateTimeZoneHandling = previousDateTimeZoneHandling.Value; + + return value; } /// @@ -447,8 +505,36 @@ namespace Newtonsoft.Json { ValidationUtils.ArgumentNotNull(jsonWriter, "jsonWriter"); + // set serialization options onto writer + Formatting? previousFormatting = null; + if (_formatting != null && jsonWriter.Formatting != _formatting) + { + previousFormatting = jsonWriter.Formatting; + jsonWriter.Formatting = _formatting.Value; + } + DateFormatHandling? previousDateFormatHandling = null; + if (_dateFormatHandling != null && jsonWriter.DateFormatHandling != _dateFormatHandling) + { + previousDateFormatHandling = jsonWriter.DateFormatHandling; + jsonWriter.DateFormatHandling = _dateFormatHandling.Value; + } + DateTimeZoneHandling? previousDateTimeZoneHandling = null; + if (_dateTimeZoneHandling != null && jsonWriter.DateTimeZoneHandling != _dateTimeZoneHandling) + { + previousDateTimeZoneHandling = jsonWriter.DateTimeZoneHandling; + jsonWriter.DateTimeZoneHandling = _dateTimeZoneHandling.Value; + } + JsonSerializerInternalWriter serializerWriter = new JsonSerializerInternalWriter(this); serializerWriter.Serialize(jsonWriter, value); + + // reset writer back to previous options + if (previousFormatting != null) + jsonWriter.Formatting = previousFormatting.Value; + if (previousDateFormatHandling != null) + jsonWriter.DateFormatHandling = previousDateFormatHandling.Value; + if (previousDateTimeZoneHandling != null) + jsonWriter.DateTimeZoneHandling = previousDateTimeZoneHandling.Value; } internal JsonConverter GetMatchingConverter(Type type) diff --git a/Src/Newtonsoft.Json/JsonSerializerSettings.cs b/Src/Newtonsoft.Json/JsonSerializerSettings.cs index 1d18e03..7ab24a3 100644 --- a/Src/Newtonsoft.Json/JsonSerializerSettings.cs +++ b/Src/Newtonsoft.Json/JsonSerializerSettings.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Runtime.Serialization.Formatters; using System.Text; @@ -25,6 +26,16 @@ namespace Newtonsoft.Json internal const FormatterAssemblyStyle DefaultTypeNameAssemblyFormat = FormatterAssemblyStyle.Simple; internal static readonly StreamingContext DefaultContext; + internal const Formatting DefaultFormatting = Formatting.None; + internal const DateFormatHandling DefaultDateFormatHandling = DateFormatHandling.IsoDateFormat; + internal const DateTimeZoneHandling DefaultDateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; + internal static readonly CultureInfo DefaultCulture; + + internal Formatting? _formatting; + internal DateFormatHandling? _dateFormatHandling; + internal DateTimeZoneHandling? _dateTimeZoneHandling; + internal CultureInfo _culture; + /// /// Gets or sets how reference loops (e.g. a class referencing itself) is handled. /// @@ -116,9 +127,34 @@ namespace Newtonsoft.Json /// The context. public StreamingContext Context { get; set; } + public Formatting Formatting + { + get { return _formatting ?? DefaultFormatting; } + set { _formatting = value; } + } + + public DateFormatHandling DateFormatHandling + { + get { return _dateFormatHandling ?? DefaultDateFormatHandling; } + set { _dateFormatHandling = value; } + } + + public DateTimeZoneHandling DateTimeZoneHandling + { + get { return _dateTimeZoneHandling ?? DefaultDateTimeZoneHandling; } + set { _dateTimeZoneHandling = value; } + } + + public CultureInfo Culture + { + get { return _culture ?? DefaultCulture; } + set { _culture = value; } + } + static JsonSerializerSettings() { DefaultContext = new StreamingContext(); + DefaultCulture = CultureInfo.InvariantCulture; } /// @@ -135,6 +171,7 @@ namespace Newtonsoft.Json TypeNameHandling = DefaultTypeNameHandling; TypeNameAssemblyFormat = DefaultTypeNameAssemblyFormat; Context = DefaultContext; + Converters = new List(); } } diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index eb792c2..f409175 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -34,24 +34,24 @@ using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json { - /// - /// Represents a reader that provides fast, non-cached, forward-only access to serialized Json data. - /// - public class JsonTextReader : JsonReader, IJsonLineInfo + internal enum ReadType { - private enum ReadType - { - Read, - ReadAsInt32, - ReadAsBytes, - ReadAsDecimal, + Read, + ReadAsInt32, + ReadAsBytes, + ReadAsString, + ReadAsDecimal, + ReadAsDateTime, #if !NET20 - ReadAsDateTimeOffset + ReadAsDateTimeOffset #endif - } - - private ReadType _readType; + } + /// + /// Represents a reader that provides fast, non-cached, forward-only access to JSON text data. + /// + public class JsonTextReader : JsonReader, IJsonLineInfo + { private readonly TextReader _reader; private char[] _chars; @@ -124,20 +124,117 @@ namespace Newtonsoft.Json SetToken(JsonToken.Bytes, data); } + else if (_readType == ReadType.ReadAsString) + { + string text = _stringReference.ToString(); + + SetToken(JsonToken.String, text); + QuoteChar = quote; + } else { string text = _stringReference.ToString(); - if (text.StartsWith("/Date(", StringComparison.Ordinal) && text.EndsWith(")/", StringComparison.Ordinal)) + if (text.Length > 0) { - ParseDate(text); + if (text[0] == '/') + { + if (text.StartsWith("/Date(", StringComparison.Ordinal) && text.EndsWith(")/", StringComparison.Ordinal)) + { + ParseDateMicrosoft(text); + return; + } + } + else if (char.IsDigit(text[0]) && text.Length >= 19 && text.Length <= 40) + { + if (ParseDateIso(text)) + return; + } } - else + + SetToken(JsonToken.String, text); + QuoteChar = quote; + } + } + + private bool ParseDateIso(string text) + { +#if !NET20 + if (_readType == ReadType.ReadAsDateTimeOffset) + { + DateTimeOffset dateTimeOffset; + if (DateTimeOffset.TryParseExact(text, "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset)) { - SetToken(JsonToken.String, text); - QuoteChar = quote; + SetToken(JsonToken.Date, dateTimeOffset); + return true; } } + else +#endif + { + DateTime dateTime; + if (DateTime.TryParseExact(text, "yyyy-MM-ddTHH:mm:ss.FFFFFFFK", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTime)) + { + dateTime = JsonConvert.EnsureDateTime(dateTime, DateTimeZoneHandling); + + SetToken(JsonToken.Date, dateTime); + return true; + } + } + + return false; + } + + private void ParseDateMicrosoft(string text) + { + string value = text.Substring(6, text.Length - 8); + DateTimeKind kind = DateTimeKind.Utc; + + int index = value.IndexOf('+', 1); + + if (index == -1) + index = value.IndexOf('-', 1); + + TimeSpan offset = TimeSpan.Zero; + + if (index != -1) + { + kind = DateTimeKind.Local; + offset = ReadOffset(value.Substring(index)); + value = value.Substring(0, index); + } + + long javaScriptTicks = long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); + + DateTime utcDateTime = JsonConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks); + +#if !NET20 + if (_readType == ReadType.ReadAsDateTimeOffset) + { + SetToken(JsonToken.Date, new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset)); + } + else +#endif + { + DateTime dateTime; + + switch (kind) + { + case DateTimeKind.Unspecified: + dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified); + break; + case DateTimeKind.Local: + dateTime = utcDateTime.ToLocalTime(); + break; + default: + dateTime = utcDateTime; + break; + } + + dateTime = JsonConvert.EnsureDateTime(dateTime, DateTimeZoneHandling); + + SetToken(JsonToken.Date, dateTime); + } } private static void BlockCopyChars(char[] src, int srcOffset, char[] dst, int dstOffset, int count) @@ -267,56 +364,6 @@ namespace Newtonsoft.Json return offset; } - private void ParseDate(string text) - { - string value = text.Substring(6, text.Length - 8); - DateTimeKind kind = DateTimeKind.Utc; - - int index = value.IndexOf('+', 1); - - if (index == -1) - index = value.IndexOf('-', 1); - - TimeSpan offset = TimeSpan.Zero; - - if (index != -1) - { - kind = DateTimeKind.Local; - offset = ReadOffset(value.Substring(index)); - value = value.Substring(0, index); - } - - long javaScriptTicks = long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture); - - DateTime utcDateTime = JsonConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks); - -#if !NET20 - if (_readType == ReadType.ReadAsDateTimeOffset) - { - SetToken(JsonToken.Date, new DateTimeOffset(utcDateTime.Add(offset).Ticks, offset)); - } - else -#endif - { - DateTime dateTime; - - switch (kind) - { - case DateTimeKind.Unspecified: - dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified); - break; - case DateTimeKind.Local: - dateTime = utcDateTime.ToLocalTime(); - break; - default: - dateTime = utcDateTime; - break; - } - - SetToken(JsonToken.Date, dateTime); - } - } - /// /// Reads the next JSON token from the stream. /// @@ -336,34 +383,6 @@ namespace Newtonsoft.Json return true; } - private bool IsWrappedInTypeObject() - { - _readType = ReadType.Read; - - if (TokenType == JsonToken.StartObject) - { - if (!ReadInternal()) - throw CreateReaderException(this, "Unexpected end when reading bytes."); - - if (Value.ToString() == "$type") - { - ReadInternal(); - if (Value != null && Value.ToString().StartsWith("System.Byte[]")) - { - ReadInternal(); - if (Value.ToString() == "$value") - { - return true; - } - } - } - - throw CreateReaderException(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, JsonToken.StartObject)); - } - - return false; - } - /// /// Reads the next JSON token from the stream as a . /// @@ -372,61 +391,7 @@ namespace Newtonsoft.Json /// public override byte[] ReadAsBytes() { - _readType = ReadType.ReadAsBytes; - - do - { - if (!ReadInternal()) - { - SetToken(JsonToken.None); - return null; - } - } while (TokenType == JsonToken.Comment); - - if (IsWrappedInTypeObject()) - { - byte[] data = ReadAsBytes(); - ReadInternal(); - SetToken(JsonToken.Bytes, data); - return data; - } - - if (TokenType == JsonToken.Null) - return null; - - if (TokenType == JsonToken.Bytes) - return (byte[])Value; - - if (TokenType == JsonToken.StartArray) - { - List data = new List(); - - while (ReadInternal()) - { - switch (TokenType) - { - case JsonToken.Integer: - data.Add(Convert.ToByte(Value, CultureInfo.InvariantCulture)); - break; - case JsonToken.EndArray: - byte[] d = data.ToArray(); - SetToken(JsonToken.Bytes, d); - return d; - case JsonToken.Comment: - // skip - break; - default: - throw CreateReaderException(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); - } - } - - throw CreateReaderException(this, "Unexpected end when reading bytes."); - } - - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + return ReadAsBytesInternal(); } /// @@ -435,41 +400,7 @@ namespace Newtonsoft.Json /// A . This method will return null at the end of an array. public override decimal? ReadAsDecimal() { - _readType = ReadType.ReadAsDecimal; - - do - { - if (!ReadInternal()) - { - SetToken(JsonToken.None); - return null; - } - } while (TokenType == JsonToken.Comment); - - if (TokenType == JsonToken.Float) - return (decimal?)Value; - - if (TokenType == JsonToken.Null) - return null; - - decimal d; - if (TokenType == JsonToken.String) - { - if (decimal.TryParse((string)Value, NumberStyles.Number, Culture, out d)) - { - SetToken(JsonToken.Float, d); - return d; - } - else - { - throw CreateReaderException(this, "Could not convert string to decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } - - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Unexpected token when reading decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + return ReadAsDecimalInternal(); } /// @@ -478,41 +409,25 @@ namespace Newtonsoft.Json /// A . This method will return null at the end of an array. public override int? ReadAsInt32() { - _readType = ReadType.ReadAsInt32; - - do - { - if (!ReadInternal()) - { - SetToken(JsonToken.None); - return null; - } - } while (TokenType == JsonToken.Comment); - - if (TokenType == JsonToken.Integer) - return (int?)Value; - - if (TokenType == JsonToken.Null) - return null; - - int i; - if (TokenType == JsonToken.String) - { - if (int.TryParse((string)Value, NumberStyles.Integer, Culture, out i)) - { - SetToken(JsonToken.Integer, i); - return i; - } - else - { - throw CreateReaderException(this, "Could not convert string to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } + return ReadAsInt32Internal(); + } - if (TokenType == JsonToken.EndArray) - return null; + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override string ReadAsString() + { + return ReadAsStringInternal(); + } - throw CreateReaderException(this, "Unexpected token when reading integer: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override DateTime? ReadAsDateTime() + { + return ReadAsDateTimeInternal(); } #if !NET20 @@ -522,45 +437,11 @@ namespace Newtonsoft.Json /// A . This method will return null at the end of an array. public override DateTimeOffset? ReadAsDateTimeOffset() { - _readType = ReadType.ReadAsDateTimeOffset; - - do - { - if (!ReadInternal()) - { - SetToken(JsonToken.None); - return null; - } - } while (TokenType == JsonToken.Comment); - - if (TokenType == JsonToken.Date) - return (DateTimeOffset)Value; - - if (TokenType == JsonToken.Null) - return null; - - DateTimeOffset dt; - if (TokenType == JsonToken.String) - { - if (DateTimeOffset.TryParse((string)Value, Culture, DateTimeStyles.None, out dt)) - { - SetToken(JsonToken.Date, dt); - return dt; - } - else - { - throw CreateReaderException(this, "Could not convert string to DateTimeOffset: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } - - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Unexpected token when reading date: {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + return ReadAsDateTimeOffsetInternal(); } #endif - private bool ReadInternal() + internal override bool ReadInternal() { while (true) { diff --git a/Src/Newtonsoft.Json/JsonTextWriter.cs b/Src/Newtonsoft.Json/JsonTextWriter.cs index 7e3d1dc..1859a21 100644 --- a/Src/Newtonsoft.Json/JsonTextWriter.cs +++ b/Src/Newtonsoft.Json/JsonTextWriter.cs @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text; using System.IO; using System.Xml; @@ -426,8 +427,10 @@ namespace Newtonsoft.Json /// The value to write. public override void WriteValue(DateTime value) { + + base.WriteValue(value); - JsonConvert.WriteDateTimeString(_writer, value); + JsonConvert.WriteDateTimeString(_writer, value, DateFormatHandling); } /// @@ -455,7 +458,7 @@ namespace Newtonsoft.Json public override void WriteValue(DateTimeOffset value) { base.WriteValue(value); - WriteValueInternal(JsonConvert.ToString(value), JsonToken.Date); + WriteValueInternal(JsonConvert.ToString(value, DateFormatHandling), JsonToken.Date); } #endif diff --git a/Src/Newtonsoft.Json/JsonValidatingReader.cs b/Src/Newtonsoft.Json/JsonValidatingReader.cs index 15c5ff4..2bf2af6 100644 --- a/Src/Newtonsoft.Json/JsonValidatingReader.cs +++ b/Src/Newtonsoft.Json/JsonValidatingReader.cs @@ -390,6 +390,30 @@ namespace Newtonsoft.Json return d; } + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override string ReadAsString() + { + string s = _reader.ReadAsString(); + + ValidateCurrentToken(); + return s; + } + + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override DateTime? ReadAsDateTime() + { + DateTime? dateTime = _reader.ReadAsDateTime(); + + ValidateCurrentToken(); + return dateTime; + } + #if !NET20 /// /// Reads the next JSON token from the stream as a . diff --git a/Src/Newtonsoft.Json/JsonWriter.cs b/Src/Newtonsoft.Json/JsonWriter.cs index a4e9289..9d4e48b 100644 --- a/Src/Newtonsoft.Json/JsonWriter.cs +++ b/Src/Newtonsoft.Json/JsonWriter.cs @@ -35,58 +35,6 @@ using System.Globalization; namespace Newtonsoft.Json { - /// - /// Specifies the state of the . - /// - public enum WriteState - { - /// - /// An exception has been thrown, which has left the in an invalid state. - /// You may call the method to put the in the Closed state. - /// Any other method calls results in an being thrown. - /// - Error, - /// - /// The method has been called. - /// - Closed, - /// - /// An object is being written. - /// - Object, - /// - /// A array is being written. - /// - Array, - /// - /// A constructor is being written. - /// - Constructor, - /// - /// A property is being written. - /// - Property, - /// - /// A write method has not been called. - /// - Start - } - - /// - /// Specifies formatting options for the . - /// - public enum Formatting - { - /// - /// No special formatting is applied. This is the default. - /// - None, - /// - /// Causes child objects to be indented according to the and settings. - /// - Indented - } - /// /// Represents a writer that provides a fast, non-cached, forward-only way of generating Json data. /// @@ -252,6 +200,9 @@ namespace Newtonsoft.Json } } + private DateFormatHandling _dateFormatHandling; + private DateTimeZoneHandling _dateTimeZoneHandling; + /// /// Indicates how the output is formatted. /// @@ -261,6 +212,18 @@ namespace Newtonsoft.Json set { _formatting = value; } } + public DateFormatHandling DateFormatHandling + { + get { return _dateFormatHandling; } + set { _dateFormatHandling = value; } + } + + public DateTimeZoneHandling DateTimeZoneHandling + { + get { return _dateTimeZoneHandling; } + set { _dateTimeZoneHandling = value; } + } + /// /// Creates an instance of the JsonWriter class. /// @@ -269,6 +232,7 @@ namespace Newtonsoft.Json _stack = new List(4); _currentState = State.Start; _formatting = Formatting.None; + _dateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; CloseOutput = true; } diff --git a/Src/Newtonsoft.Json/Linq/JTokenReader.cs b/Src/Newtonsoft.Json/Linq/JTokenReader.cs index 0faf9b4..32c6bf1 100644 --- a/Src/Newtonsoft.Json/Linq/JTokenReader.cs +++ b/Src/Newtonsoft.Json/Linq/JTokenReader.cs @@ -36,57 +36,7 @@ namespace Newtonsoft.Json.Linq /// public override byte[] ReadAsBytes() { - Read(); - - if (IsWrappedInTypeObject()) - { - byte[] data = ReadAsBytes(); - Read(); - SetToken(JsonToken.Bytes, data); - return data; - } - - // attempt to convert possible base 64 string to bytes - if (TokenType == JsonToken.String) - { - string s = (string) Value; - byte[] data = (s.Length == 0) ? new byte[0] : Convert.FromBase64String(s); - SetToken(JsonToken.Bytes, data); - } - - if (TokenType == JsonToken.Null) - return null; - if (TokenType == JsonToken.Bytes) - return (byte[])Value; - - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); - } - - private bool IsWrappedInTypeObject() - { - if (TokenType == JsonToken.StartObject) - { - Read(); - if (Value.ToString() == "$type") - { - Read(); - if (Value != null && Value.ToString().StartsWith("System.Byte[]")) - { - Read(); - if (Value.ToString() == "$value") - { - return true; - } - } - } - - throw CreateReaderException(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, JsonToken.StartObject)); - } - - return false; + return ReadAsBytesInternal(); } /// @@ -95,35 +45,7 @@ namespace Newtonsoft.Json.Linq /// A . This method will return null at the end of an array. public override decimal? ReadAsDecimal() { - Read(); - - if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) - { - SetToken(JsonToken.Float, Convert.ToDecimal(Value, CultureInfo.InvariantCulture)); - return (decimal) Value; - } - - if (TokenType == JsonToken.Null) - return null; - - decimal d; - if (TokenType == JsonToken.String) - { - if (decimal.TryParse((string)Value, NumberStyles.Number, Culture, out d)) - { - SetToken(JsonToken.Float, d); - return d; - } - else - { - throw CreateReaderException(this, "Could not convert string to decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } - - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Error reading decimal. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + return ReadAsDecimalInternal(); } /// @@ -132,35 +54,25 @@ namespace Newtonsoft.Json.Linq /// A . This method will return null at the end of an array. public override int? ReadAsInt32() { - Read(); - - if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) - { - SetToken(JsonToken.Integer, Convert.ToInt32(Value, CultureInfo.InvariantCulture)); - return (int)Value; - } - - if (TokenType == JsonToken.Null) - return null; - - int i; - if (TokenType == JsonToken.String) - { - if (int.TryParse((string)Value, NumberStyles.Integer, Culture, out i)) - { - SetToken(JsonToken.Integer, i); - return i; - } - else - { - throw CreateReaderException(this, "Could not convert string to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } - } + return ReadAsInt32Internal(); + } - if (TokenType == JsonToken.EndArray) - return null; + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override string ReadAsString() + { + return ReadAsStringInternal(); + } - throw CreateReaderException(this, "Error reading integer. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + /// + /// Reads the next JSON token from the stream as a . + /// + /// A . This method will return null at the end of an array. + public override DateTime? ReadAsDateTime() + { + return ReadAsDateTimeInternal(); } #if !NET20 @@ -170,37 +82,24 @@ namespace Newtonsoft.Json.Linq /// A . This method will return null at the end of an array. public override DateTimeOffset? ReadAsDateTimeOffset() { - Read(); - - if (TokenType == JsonToken.Date) - { - SetToken(JsonToken.Date, new DateTimeOffset((DateTime)Value)); - return (DateTimeOffset)Value; - } - - if (TokenType == JsonToken.Null) - return null; + return ReadAsDateTimeOffsetInternal(); + } +#endif - DateTimeOffset dt; - if (TokenType == JsonToken.String) + internal override bool ReadInternal() + { + if (CurrentState != State.Start) { - if (DateTimeOffset.TryParse((string)Value, Culture, DateTimeStyles.None, out dt)) - { - SetToken(JsonToken.Date, dt); - return dt; - } + JContainer container = _current as JContainer; + if (container != null && _parent != container) + return ReadInto(container); else - { - throw CreateReaderException(this, "Could not convert string to DateTimeOffset: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); - } + return ReadOver(_current); } - if (TokenType == JsonToken.EndArray) - return null; - - throw CreateReaderException(this, "Error reading date. Expected date but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); + SetToken(_current); + return true; } -#endif /// /// Reads the next JSON token from the stream. @@ -210,17 +109,9 @@ namespace Newtonsoft.Json.Linq /// public override bool Read() { - if (CurrentState != State.Start) - { - JContainer container = _current as JContainer; - if (container != null && _parent != container) - return ReadInto(container); - else - return ReadOver(_current); - } + _readType = ReadType.Read; - SetToken(_current); - return true; + return ReadInternal(); } private bool ReadOver(JToken t) diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index ca9b1fa..42f64b0 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -92,7 +92,14 @@ + + + + + + + @@ -114,7 +121,6 @@ - @@ -219,6 +225,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index 54c0188..22bd2a8 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -104,8 +104,15 @@ + + + + + + + @@ -133,7 +140,6 @@ - @@ -234,6 +240,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj index 1cd5868..7ea6e09 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj @@ -98,14 +98,16 @@ - + + + @@ -116,6 +118,7 @@ + @@ -233,6 +236,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj index 6825dd5..3014bec 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.WindowsPhone.csproj @@ -71,14 +71,16 @@ - + + + @@ -89,6 +91,7 @@ + @@ -203,6 +206,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 529ca24..d1b70ad 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -103,7 +103,11 @@ + + + + @@ -132,7 +136,6 @@ - @@ -233,6 +236,7 @@ + diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 0e73afb..32ff8a2 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -85,7 +85,7 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.0.8.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.0.8.14625")] +[assembly: AssemblyFileVersion("4.0.8.14704")] #endif [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index 9dd8532..db38243 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -47,17 +47,6 @@ namespace Newtonsoft.Json.Serialization Linq } - internal enum ReadType - { - Read, - ReadAsInt32, - ReadAsDecimal, - ReadAsBytes, -#if !NET20 - ReadAsDateTimeOffset -#endif - } - /// /// Contract details for a used by the . /// @@ -204,6 +193,14 @@ namespace Newtonsoft.Json.Serialization { InternalReadType = ReadType.ReadAsDecimal; } + else if (NonNullableUnderlyingType == typeof(string)) + { + InternalReadType = ReadType.ReadAsString; + } + else if (NonNullableUnderlyingType == typeof(DateTime)) + { + InternalReadType = ReadType.ReadAsDateTime; + } #if !NET20 else if (NonNullableUnderlyingType == typeof(DateTimeOffset)) { diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 40054cc..2a81a8a 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -1134,6 +1134,12 @@ namespace Newtonsoft.Json.Serialization case ReadType.ReadAsBytes: reader.ReadAsBytes(); break; + case ReadType.ReadAsString: + reader.ReadAsString(); + break; + case ReadType.ReadAsDateTime: + reader.ReadAsDateTime(); + break; #if !NET20 case ReadType.ReadAsDateTimeOffset: reader.ReadAsDateTimeOffset(); diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs index d73090c..f35343c 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs @@ -24,6 +24,7 @@ #endregion using System; +using System.Globalization; using System.Runtime.Serialization.Formatters; using Newtonsoft.Json.Utilities; using System.Runtime.Serialization; @@ -125,6 +126,24 @@ namespace Newtonsoft.Json.Serialization set { _serializer.Context = value; } } + public override Formatting Formatting + { + get { return _serializer.Formatting; } + set { _serializer.Formatting = value; } + } + + public override DateFormatHandling DateFormatHandling + { + get { return _serializer.DateFormatHandling; } + set { _serializer.DateFormatHandling = value; } + } + + public override DateTimeZoneHandling DateTimeZoneHandling + { + get { return _serializer.DateTimeZoneHandling; } + set { _serializer.DateTimeZoneHandling = value; } + } + internal JsonSerializerInternalBase GetInternalSerializer() { if (_serializerReader != null) diff --git a/Src/Newtonsoft.Json/WriteState.cs b/Src/Newtonsoft.Json/WriteState.cs new file mode 100644 index 0000000..8bfc577 --- /dev/null +++ b/Src/Newtonsoft.Json/WriteState.cs @@ -0,0 +1,41 @@ +using System; + +namespace Newtonsoft.Json +{ + /// + /// Specifies the state of the . + /// + public enum WriteState + { + /// + /// An exception has been thrown, which has left the in an invalid state. + /// You may call the method to put the in the Closed state. + /// Any other method calls results in an being thrown. + /// + Error, + /// + /// The method has been called. + /// + Closed, + /// + /// An object is being written. + /// + Object, + /// + /// A array is being written. + /// + Array, + /// + /// A constructor is being written. + /// + Constructor, + /// + /// A property is being written. + /// + Property, + /// + /// A write method has not been called. + /// + Start + } +} \ No newline at end of file -- cgit v1.2.3