diff --git a/Src/Newtonsoft.Json.Tests/AspNetAjaxDateTimeConverterTest.cs b/Src/Newtonsoft.Json.Tests/AspNetAjaxDateTimeConverterTest.cs deleted file mode 100644 index f76ded52..00000000 --- a/Src/Newtonsoft.Json.Tests/AspNetAjaxDateTimeConverterTest.cs +++ /dev/null @@ -1,70 +0,0 @@ -#region License -// Copyright (c) 2007 James Newton-King -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -#endregion - -using System; -using System.Collections.Generic; -using System.Text; -using NUnit.Framework; -using Newtonsoft.Json; -using System.IO; -using Newtonsoft.Json.Converters; - -namespace Newtonsoft.Json.Tests -{ - [TestFixture] - public class AspNetAjaxDateTimeConverterTest - { - public class DateTimeTestClass - { - public string PreField; - public DateTime DateTimeField; - public string PostField; - } - - [Test] - public void Serialize() - { - DateTimeTestClass c = new DateTimeTestClass(); - c.DateTimeField = new DateTime(2008, 12, 12, 12, 12, 12, 12); - c.PreField = "Pre"; - c.PostField = "Post"; - - string json = JavaScriptConvert.SerializeObject(c, new AspNetAjaxDateTimeConverter()); - - Assert.AreEqual(@"{""PreField"":""Pre"",""DateTimeField"":""@1229083932012@"",""PostField"":""Post""}", json); - } - - [Test] - public void DeSerialize() - { - DateTimeTestClass c = - JavaScriptConvert.DeserializeObject(@"{""PreField"":""Pre"",""DateTimeField"":""@1229083932012@"",""PostField"":""Post""}", new AspNetAjaxDateTimeConverter()); - - Assert.AreEqual(new DateTime(2008, 12, 12, 12, 12, 12, 12), c.DateTimeField); - Assert.AreEqual("Pre", c.PreField); - Assert.AreEqual("Post", c.PostField); - } - } -} \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/DateTimeTestClass.cs b/Src/Newtonsoft.Json.Tests/DateTimeTestClass.cs new file mode 100644 index 00000000..558a7404 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/DateTimeTestClass.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Newtonsoft.Json.Tests +{ + public class DateTimeTestClass + { + public string PreField { get; set; } + public DateTime DateTimeField { get; set; } + public string PostField { get; set; } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/IsoDateTimeConverterTests.cs b/Src/Newtonsoft.Json.Tests/IsoDateTimeConverterTests.cs new file mode 100644 index 00000000..8b3580db --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/IsoDateTimeConverterTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Utilities; +using System.Globalization; + +namespace Newtonsoft.Json.Tests +{ + public class IsoDateTimeConverterTests : TestFixtureBase + { + [Test] + public void SerializeDateTime() + { + IsoDateTimeConverter converter = new IsoDateTimeConverter(); + + DateTime d = new DateTime(2000, 12, 15, 22, 11, 3, 55, DateTimeKind.Utc); + string result; + + result = JavaScriptConvert.SerializeObject(d, converter); + Assert.AreEqual(@"""2000-12-15T22:11:03.0550000Z""", result); + + Assert.AreEqual(d, JavaScriptConvert.DeserializeObject(result, converter)); + + d = new DateTime(2000, 12, 15, 22, 11, 3, 55, DateTimeKind.Local); + result = JavaScriptConvert.SerializeObject(d, converter); + Assert.AreEqual(@"""2000-12-15T22:11:03.0550000" + d.GetLocalOffset() + @"""", result); + } + + [Test] + public void SerializeDateTimeOffset() + { + IsoDateTimeConverter converter = new IsoDateTimeConverter(); + + DateTimeOffset d = new DateTimeOffset(2000, 12, 15, 22, 11, 3, 55, TimeSpan.Zero); + string result; + + result = JavaScriptConvert.SerializeObject(d, converter); + Assert.AreEqual(@"""2000-12-15T22:11:03.0550000+00:00""", result); + + Assert.AreEqual(d, JavaScriptConvert.DeserializeObject(result, converter)); + } + + [Test] + public void SerializeUTC() + { + DateTimeTestClass c = new DateTimeTestClass(); + c.DateTimeField = new DateTime(2008, 12, 12, 12, 12, 12, 0, DateTimeKind.Utc).ToLocalTime(); + c.PreField = "Pre"; + c.PostField = "Post"; + string json = JavaScriptConvert.SerializeObject(c, new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AssumeUniversal }); + Assert.AreEqual(@"{""PreField"":""Pre"",""DateTimeField"":""2008-12-12T12:12:12.0000000Z"",""PostField"":""Post""}", json); + + //test the other edge case too + c.DateTimeField = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Utc).ToLocalTime(); + c.PreField = "Pre"; + c.PostField = "Post"; + json = JavaScriptConvert.SerializeObject(c, new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AssumeUniversal }); + Assert.AreEqual(@"{""PreField"":""Pre"",""DateTimeField"":""2008-01-01T01:01:01.0000000Z"",""PostField"":""Post""}", json); + } + + [Test] + public void DeserializeUTC() + { + DateTimeTestClass c = + JavaScriptConvert.DeserializeObject(@"{""PreField"":""Pre"",""DateTimeField"":""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("Pre", c.PreField); + Assert.AreEqual("Post", c.PostField); + + DateTimeTestClass c2 = + JavaScriptConvert.DeserializeObject(@"{""PreField"":""Pre"",""DateTimeField"":""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("Pre", c2.PreField); + Assert.AreEqual("Post", c2.PostField); + } + + [Test] + public void SerializeShouldChangeNonUTCDates() + { + DateTimeTestClass c = new DateTimeTestClass(); + c.DateTimeField = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Local); + c.PreField = "Pre"; + c.PostField = "Post"; + string json = JavaScriptConvert.SerializeObject(c, new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AssumeUniversal }); //note that this fails without the Utc converter... + c.DateTimeField = new DateTime(2008, 1, 1, 1, 1, 1, 0, DateTimeKind.Utc); + string json2 = JavaScriptConvert.SerializeObject(c, new IsoDateTimeConverter() { DateTimeStyles = DateTimeStyles.AssumeUniversal }); + Assert.AreNotEqual(json, json2); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JavaScriptConvertTest.cs b/Src/Newtonsoft.Json.Tests/JavaScriptConvertTest.cs index 63bf85b1..917e23ae 100644 --- a/Src/Newtonsoft.Json.Tests/JavaScriptConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JavaScriptConvertTest.cs @@ -6,8 +6,7 @@ using NUnit.Framework; namespace Newtonsoft.Json.Tests { - [TestFixture] - public class JavaScriptConvertTest + public class JavaScriptConvertTest : TestFixtureBase { [Test] public void EscapeJavaScriptString() diff --git a/Src/Newtonsoft.Json.Tests/JavaScriptDateTimeConverterTests.cs b/Src/Newtonsoft.Json.Tests/JavaScriptDateTimeConverterTests.cs new file mode 100644 index 00000000..cf7288be --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/JavaScriptDateTimeConverterTests.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Newtonsoft.Json.Converters; + +namespace Newtonsoft.Json.Tests +{ + public class JavaScriptDateTimeConverterTests : TestFixtureBase + { + [Test] + public void SerializeDateTime() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + + DateTime d = new DateTime(2000, 12, 15, 22, 11, 3, 55, DateTimeKind.Utc); + string result; + + result = JavaScriptConvert.SerializeObject(d, converter); + Assert.AreEqual("new Date(976918263055)", result); + } + + [Test] + public void SerializeDateTimeOffset() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + + DateTimeOffset now = new DateTimeOffset(2000, 12, 15, 22, 11, 3, 55, TimeSpan.Zero); + string result; + + result = JavaScriptConvert.SerializeObject(now, converter); + Assert.AreEqual("new Date(976918263055)", result); + } + + [Test] + public void DeserializeDateTime() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + + DateTime result = JavaScriptConvert.DeserializeObject("new Date(976918263055)", converter); + Assert.AreEqual(new DateTime(2000, 12, 15, 22, 11, 3, 55, DateTimeKind.Utc), result); + } + + [Test] + public void DeserializeDateTimeOffset() + { + JavaScriptDateTimeConverter converter = new JavaScriptDateTimeConverter(); + DateTimeOffset start = new DateTimeOffset(2000, 12, 15, 22, 11, 3, 55, TimeSpan.Zero); + + string json = JavaScriptConvert.SerializeObject(start, converter); + + DateTimeOffset result = JavaScriptConvert.DeserializeObject(json, converter); + Assert.AreEqual(new DateTimeOffset(2000, 12, 15, 22, 11, 3, 55, TimeSpan.Zero), result); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JsonReaderTest.cs b/Src/Newtonsoft.Json.Tests/JsonReaderTest.cs index 5bb32aa8..f94a88aa 100644 --- a/Src/Newtonsoft.Json.Tests/JsonReaderTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonReaderTest.cs @@ -32,8 +32,7 @@ using System.IO; namespace Newtonsoft.Json.Tests { - [TestFixture] - public class JsonReaderTest + public class JsonReaderTest : TestFixtureBase { [Test] public void YahooFinance() diff --git a/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs index c1361298..a8ef48b3 100644 --- a/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs @@ -35,20 +35,25 @@ using System.Xml.Serialization; using System.Collections.ObjectModel; using System.Runtime.Serialization.Json; using System.Net.Mail; +using System.Web.Script.Serialization; namespace Newtonsoft.Json.Tests { public class Product { public string Name; - public DateTime Expiry; + public DateTime Expiry = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); public decimal Price; public string[] Sizes; public override bool Equals(object obj) { if (obj is Product) - return ((Product)obj).Name == Name; + { + Product p = (Product)obj; + + return (p.Name == Name && p.Expiry == Expiry && p.Price == Price); + } return base.Equals(obj); } @@ -74,7 +79,7 @@ namespace Newtonsoft.Json.Tests public class Store { public StoreColor Color = StoreColor.Yellow; - public DateTime Establised = new DateTime(2010, 1, 22); + public DateTimeOffset Establised = new DateTimeOffset(2010, 1, 22, 1, 1, 1, TimeSpan.Zero); public double Width = 1.1; public int Employees = 999; public int[] RoomsPerFloor = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; @@ -94,7 +99,7 @@ namespace Newtonsoft.Json.Tests Product rocket = new Product(); rocket.Name = "Rocket"; - rocket.Expiry = new DateTime(2000, 2, 2, 23, 1, 30); + rocket.Expiry = new DateTime(2000, 2, 2, 23, 1, 30, DateTimeKind.Utc); Product alien = new Product(); alien.Name = "Alien"; @@ -111,8 +116,7 @@ namespace Newtonsoft.Json.Tests White } - [TestFixture] - public class JsonSerializerTest + public class JsonSerializerTest : TestFixtureBase { [Test] public void PersonTypedObjectDeserialization() @@ -626,6 +630,46 @@ keyword such as type of business."" Assert.AreEqual(dataContractResult[0], jsonNetResult[0]); } + [Test] + public void DateTime() + { + List testDates = new List { + new DateTime(100, 1, 1, 1, 1, 1, DateTimeKind.Local), + new DateTime(100, 1, 1, 1, 1, 1, DateTimeKind.Unspecified), + new DateTime(100, 1, 1, 1, 1, 1, DateTimeKind.Utc), + new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Local), + new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Unspecified), + new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Utc), + }; + string result; + + + MemoryStream ms = new MemoryStream(); + DataContractJsonSerializer s = new DataContractJsonSerializer(typeof(List)); + s.WriteObject(ms, testDates); + ms.Seek(0, SeekOrigin.Begin); + StreamReader sr = new StreamReader(ms); + + string expected = sr.ReadToEnd(); + + result = JavaScriptConvert.SerializeObject(testDates); + Assert.AreEqual(expected, result); + } + + [Test] + public void DateTimeOffset() + { + 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 = JavaScriptConvert.SerializeObject(testDates); + Assert.AreEqual(@"[""\/Date(-59011455539000+0000)\/"",""\/Date(946688461000+0000)\/"",""\/Date(946641661000+1300)\/"",""\/Date(946701061000-0330)\/""]", result); + } + [Test] public void NonStringKeyDictionary() { @@ -645,10 +689,27 @@ keyword such as type of business."" [Test] public void AnonymousObjectSerialization() { - var anonymous = new { StringValue = "I am a string", IntValue = int.MaxValue }; + var anonymous = + new + { + StringValue = "I am a string", + IntValue = int.MaxValue, + NestedAnonymous = new { NestedValue = byte.MaxValue }, + NestedArray = new[] { 1, 2 }, + Product = new Product() { Name = "TestProduct" } + }; string json = JavaScriptConvert.SerializeObject(anonymous); - Assert.AreEqual(@"{""StringValue"":""I am a string"",""IntValue"":2147483647}", json); + Assert.AreEqual(@"{""StringValue"":""I am a string"",""IntValue"":2147483647,""NestedAnonymous"":{""NestedValue"":255},""NestedArray"":[1,2],""Product"":{""Name"":""TestProduct"",""Expiry"":""\/Date(946684800000)\/"",""Price"":0,""Sizes"":null}}", json); + + anonymous = JavaScriptConvert.DeserializeAnonymousType(json, anonymous); + Assert.AreEqual("I am a string", anonymous.StringValue); + Assert.AreEqual(int.MaxValue, anonymous.IntValue); + Assert.AreEqual(255, anonymous.NestedAnonymous.NestedValue); + Assert.AreEqual(2, anonymous.NestedArray.Length); + Assert.AreEqual(1, anonymous.NestedArray[0]); + Assert.AreEqual(2, anonymous.NestedArray[1]); + Assert.AreEqual("TestProduct", anonymous.Product.Name); } [Test] @@ -668,7 +729,7 @@ keyword such as type of business."" jsonSerializer.Serialize(sw, collection); - Assert.AreEqual(@"[{""Name"":""Test1"",""Expiry"":new Date(-59011459200000),""Price"":0,""Sizes"":null},{""Name"":""Test2"",""Expiry"":new Date(-59011459200000),""Price"":0,""Sizes"":null},{""Name"":""Test3"",""Expiry"":new Date(-59011459200000),""Price"":0,""Sizes"":null}]", + Assert.AreEqual(@"[{""Name"":""Test1"",""Expiry"":""\/Date(946684800000)\/"",""Price"":0,""Sizes"":null},{""Name"":""Test2"",""Expiry"":""\/Date(946684800000)\/"",""Price"":0,""Sizes"":null},{""Name"":""Test3"",""Expiry"":""\/Date(946684800000)\/"",""Price"":0,""Sizes"":null}]", sw.GetStringBuilder().ToString()); ProductCollection collectionNew = (ProductCollection)jsonSerializer.Deserialize(new JsonReader(new StringReader(sw.GetStringBuilder().ToString())), typeof(ProductCollection)); @@ -687,13 +748,18 @@ keyword such as type of business."" StringWriter sw = new StringWriter(); jsonSerializer.Serialize(sw, s1); - Assert.AreEqual(@"{""Color"":2,""Establised"":new Date(1264118400000),""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"",""Expiry"":new Date(949532490000),""Price"":0},{""Name"":""Alien"",""Expiry"":new Date(-59011459200000),""Price"":0}]}", sw.GetStringBuilder().ToString()); + //JavaScriptConvert.ConvertDateTimeToJavaScriptTicks(s1.Establised.DateTime) + + Assert.AreEqual(@"{""Color"":2,""Establised"":""\/Date(1264122061000+0000)\/"",""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"",""Expiry"":""\/Date(949532490000)\/"",""Price"":0},{""Name"":""Alien"",""Expiry"":""\/Date(946684800000)\/"",""Price"":0}]}", sw.GetStringBuilder().ToString()); Store s2 = (Store)jsonSerializer.Deserialize(new JsonReader(new StringReader("{}")), typeof(Store)); Assert.AreEqual("\r\n\t\f\b?{\\r\\n\"\'", s2.Escape); Store s3 = (Store)jsonSerializer.Deserialize(new JsonReader(new StringReader(@"{""Escape"":null}")), typeof(Store)); Assert.AreEqual("\r\n\t\f\b?{\\r\\n\"\'", s3.Escape); + + Store s4 = (Store)jsonSerializer.Deserialize(new JsonReader(new StringReader(@"{""Color"":2,""Establised"":""\/Date(1264071600000+1300)\/"",""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"",""Expiry"":""\/Date(949485690000+1300)\/"",""Price"":0},{""Name"":""Alien"",""Expiry"":""\/Date(946638000000)\/"",""Price"":0}]}")), typeof(Store)); + Assert.AreEqual(s1.Establised, s3.Establised); } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JsonWriterTest.cs b/Src/Newtonsoft.Json.Tests/JsonWriterTest.cs index 5bb8aeb7..4ca22732 100644 --- a/Src/Newtonsoft.Json.Tests/JsonWriterTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonWriterTest.cs @@ -32,8 +32,7 @@ using System.IO; namespace Newtonsoft.Json.Tests { - [TestFixture] - public class JsonWriterTest + public class JsonWriterTest : TestFixtureBase { [Test] public void ValueFormatting() diff --git a/Src/Newtonsoft.Json.Tests/LinqToJsonTest.cs b/Src/Newtonsoft.Json.Tests/LinqToJsonTest.cs index 21df18f2..9426abc5 100644 --- a/Src/Newtonsoft.Json.Tests/LinqToJsonTest.cs +++ b/Src/Newtonsoft.Json.Tests/LinqToJsonTest.cs @@ -34,8 +34,7 @@ using System.IO; namespace Newtonsoft.Json.Tests { - [TestFixture] - public class LinqToJsonTest + public class LinqToJsonTest : TestFixtureBase { [Test] public void ObjectParse() @@ -226,12 +225,16 @@ keyword such as type of business."" [Test] public void JTokenToStringTypes() { - string json = @"{""Color"":2,""Establised"":new Date(1264118400000),""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"",""Expiry"":new Date(949532490000),""Price"":0},{""Name"":""Alien"",""Expiry"":new Date(-59011459200000),""Price"":0}]}"; + string json = @"{""Color"":2,""Establised"":new Date(1264118400000),""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"",""Expiry"":new Date(949532490000),""Price"":0},{""Name"":""Alien"",""Expiry"":new Date(-62135596800000),""Price"":0}]}"; JObject o = JObject.Parse(json); - Assert.AreEqual(@"""Establised"": new Date(1264118400000)", o.Property("Establised").ToString()); - Assert.AreEqual(@"new Date(1264118400000)", o.Property("Establised").Value.ToString()); + Assert.AreEqual(@"""Establised"": new Date( + 1264118400000 +)", o.Property("Establised").ToString()); + Assert.AreEqual(@"new Date( + 1264118400000 +)", o.Property("Establised").Value.ToString()); Assert.AreEqual(@"""Width"": 1.1", o.Property("Width").ToString()); Assert.AreEqual(@"1.1", o.Property("Width").Value.ToString()); Assert.AreEqual(@"""Open"": false", o.Property("Open").ToString()); @@ -271,13 +274,13 @@ keyword such as type of business."" JArray a = new JArray( o, - new DateTime(2000, 10, 10), + new DateTime(2000, 10, 10, 0, 0, 0, DateTimeKind.Utc), 55, new JArray( "1", 2, 3.0, - new DateTime(4, 5, 6, 7, 8, 9) + new DateTime(4, 5, 6, 7, 8, 9, DateTimeKind.Utc) ), new JConstructor( "ConstructorName", @@ -295,13 +298,13 @@ keyword such as type of business."" ""Test3"": ""Test3Value"", ""Test4"": null }, - new Date(971136000000), + ""\/Date(971136000000)\/"", 55, [ ""1"", 2, 3, - new Date(-59011459200000) + ""\/Date(-62030076711000)\/"" ], new ConstructorName( ""param1"", diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index c66a51ef..2af957f5 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -60,16 +60,23 @@ 3.5 + + 3.5 + - + + + + + diff --git a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs new file mode 100644 index 00000000..6b0b9a90 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; + +namespace Newtonsoft.Json.Tests +{ + [TestFixture] + public abstract class TestFixtureBase + { + public string GetOffset(DateTime value) + { + TimeSpan utcOffset = TimeZone.CurrentTimeZone.GetUtcOffset(value.ToLocalTime()); + + return utcOffset.Hours.ToString("+00;-00") + utcOffset.Minutes.ToString("00;00"); + } + } +} diff --git a/Src/Newtonsoft.Json/Converters/AspNetAjaxDateTimeConverter.cs b/Src/Newtonsoft.Json.Tests/UtcDateTimeConverterTest.cs similarity index 57% rename from Src/Newtonsoft.Json/Converters/AspNetAjaxDateTimeConverter.cs rename to Src/Newtonsoft.Json.Tests/UtcDateTimeConverterTest.cs index eaaf83fa..b71eb969 100644 --- a/Src/Newtonsoft.Json/Converters/AspNetAjaxDateTimeConverter.cs +++ b/Src/Newtonsoft.Json.Tests/UtcDateTimeConverterTest.cs @@ -23,37 +23,26 @@ // OTHER DEALINGS IN THE SOFTWARE. #endregion - using System; using System.Collections.Generic; using System.Text; +using NUnit.Framework; +using Newtonsoft.Json; +using System.IO; +using Newtonsoft.Json.Converters; +using System.Xml; using System.Globalization; -namespace Newtonsoft.Json.Converters +namespace Newtonsoft.Json.Tests { - public class AspNetAjaxDateTimeConverter : JsonConverter + public class IsoDateTimeConverterTest : TestFixtureBase { - public override void WriteJson(JsonWriter writer, object value) + [Test] + public void ShouldBeAbleToConvertDateTimes() { - DateTime dateTime = (DateTime)value; - long javaScriptTicks = JavaScriptConvert.ConvertDateTimeToJavaScriptTicks(dateTime); - - writer.WriteValue("@" + javaScriptTicks.ToString(null, CultureInfo.InvariantCulture) + "@"); - } - - public override object ReadJson(JsonReader reader, Type objectType) - { - string dateTimeText = (string)reader.Value; - dateTimeText = dateTimeText.Substring(1, dateTimeText.Length - 2); - - long javaScriptTicks = Convert.ToInt64(dateTimeText); - - return JavaScriptConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks); - } - - public override bool CanConvert(Type valueType) - { - return typeof(DateTime).IsAssignableFrom(valueType); + IsoDateTimeConverter conv = new IsoDateTimeConverter(); + Assert.IsTrue(conv.CanConvert(typeof(DateTime))); + Assert.IsTrue(conv.CanConvert(typeof(DateTimeOffset))); } } -} +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/XmlNodeConverterTest.cs b/Src/Newtonsoft.Json.Tests/XmlNodeConverterTest.cs index 21b4bcdb..65ef870d 100644 --- a/Src/Newtonsoft.Json.Tests/XmlNodeConverterTest.cs +++ b/Src/Newtonsoft.Json.Tests/XmlNodeConverterTest.cs @@ -29,11 +29,11 @@ using Newtonsoft.Json; using System.IO; using System.Xml; using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Tests { - [TestFixture] - public class XmlNodeConverterTest + public class XmlNodeConverterTest : TestFixtureBase { [Test] public void DocumentSerializeIndented() @@ -210,7 +210,7 @@ namespace Newtonsoft.Json.Tests string jsonText = JavaScriptConvert.SerializeXmlNode(doc); - XmlDocument deserializedDoc = (XmlDocument)JavaScriptConvert.DeerializeXmlNode(jsonText); + XmlDocument deserializedDoc = (XmlDocument)JavaScriptConvert.DeserializeXmlNode(jsonText); Assert.AreEqual(doc.InnerXml, deserializedDoc.InnerXml); @@ -242,7 +242,7 @@ namespace Newtonsoft.Json.Tests } }"; - XmlDocument doc = (XmlDocument)JavaScriptConvert.DeerializeXmlNode(jsonText); + XmlDocument doc = (XmlDocument)JavaScriptConvert.DeserializeXmlNode(jsonText); string expected = @" @@ -295,7 +295,7 @@ namespace Newtonsoft.Json.Tests string jsonText = JavaScriptConvert.SerializeXmlNode(doc); - XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeerializeXmlNode(jsonText); + XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeserializeXmlNode(jsonText); Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); } @@ -322,7 +322,7 @@ namespace Newtonsoft.Json.Tests Console.WriteLine(jsonText); - XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeerializeXmlNode(jsonText); + XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeserializeXmlNode(jsonText); Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); } @@ -330,13 +330,89 @@ namespace Newtonsoft.Json.Tests [Test] public void OtherElementDataTypes() { - string jsonText = @"{""?xml"":{""@version"":""1.0"",""@standalone"":""no""},""root"":{""person"":[{""@id"":""1"",""Float"":2.5,""Integer"":99},{""@id"":""2"",""Boolean"":true,""date"":new Date(954374400000)}]}}"; + string jsonText = @"{""?xml"":{""@version"":""1.0"",""@standalone"":""no""},""root"":{""person"":[{""@id"":""1"",""Float"":2.5,""Integer"":99},{""@id"":""2"",""Boolean"":true,""date"":""\/Date(954374400000)\/""}]}}"; - XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeerializeXmlNode(jsonText); + XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeserializeXmlNode(jsonText); - string expected = @"2.599true2000-03-30T00:00:00.0000000+13:00"; + string expected = @"2.599true2000-03-30T00:00:00Z"; Assert.AreEqual(expected, newDoc.InnerXml); } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "XmlNodeConverter can only convert JSON that begins with an object.")] + public void NoRootObject() + { + XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeserializeXmlNode(@"[1]"); + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "JSON root object has multiple properties.")] + public void RootObjectMultipleProperties() + { + XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeserializeXmlNode(@"{Prop1:1,Prop2:2}"); + } + + [Test] + public void JavaScriptConstructor() + { + string jsonText = @"{root:{r:new Date(34343, 55)}}"; + + XmlDocument newDoc = (XmlDocument)JavaScriptConvert.DeserializeXmlNode(jsonText); + + string expected = @"<-Date>34343<-Date>55"; + + Assert.AreEqual(expected, newDoc.InnerXml); + + string json = JavaScriptConvert.SerializeXmlNode(newDoc); + Assert.AreEqual(@"{""root"":{""r"":new Date(""34343"",""55"")}}", json); + } + + [Test] + public void ForceJsonArray() + { + string arrayXml = @" + + Alan + http://www.google.com + Admin + + "; + + XmlDocument arrayDoc = new XmlDocument(); + arrayDoc.LoadXml(arrayXml); + + string arrayJsonText = JavaScriptConvert.SerializeXmlNode(arrayDoc); + Assert.AreEqual(@"{""root"":{""person"":{""@id"":""1"",""name"":""Alan"",""url"":""http://www.google.com"",""role"":[""Admin""]}}}", arrayJsonText); + + arrayXml = @" + + Alan + http://www.google.com + Admin1 + Admin2 + + "; + + arrayDoc = new XmlDocument(); + arrayDoc.LoadXml(arrayXml); + + arrayJsonText = JavaScriptConvert.SerializeXmlNode(arrayDoc); + Assert.AreEqual(@"{""root"":{""person"":{""@id"":""1"",""name"":""Alan"",""url"":""http://www.google.com"",""role"":[""Admin1"",""Admin2""]}}}", arrayJsonText); + + arrayXml = @" + + Alan + http://www.google.com + Admin1 + + "; + + arrayDoc = new XmlDocument(); + arrayDoc.LoadXml(arrayXml); + + arrayJsonText = JavaScriptConvert.SerializeXmlNode(arrayDoc); + Assert.AreEqual(@"{""root"":{""person"":{""@id"":""1"",""name"":""Alan"",""url"":""http://www.google.com"",""role"":""Admin1""}}}", arrayJsonText); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs new file mode 100644 index 00000000..8d067293 --- /dev/null +++ b/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Globalization; + +namespace Newtonsoft.Json.Converters +{ + public class IsoDateTimeConverter : JsonConverter + { + private const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"; + + private DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind; + //private bool _forceUniversalTime; + + //public bool ForceUniversalTime + //{ + // get { return _forceUniversalTime; } + // set { _forceUniversalTime = value; } + //} + + public DateTimeStyles DateTimeStyles + { + get { return _dateTimeStyles; } + set { _dateTimeStyles = value; } + } + + public override void WriteJson(JsonWriter writer, object value) + { + string text; + + if (value is DateTime) + { + DateTime dateTime = (DateTime)value; + + if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal + || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal) + dateTime = dateTime.ToUniversalTime(); + + text = dateTime.ToString(DateTimeFormat, CultureInfo.InvariantCulture); + } + else + { + DateTimeOffset dateTimeOffset = (DateTimeOffset)value; + if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal + || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal) + dateTimeOffset = dateTimeOffset.ToUniversalTime(); + + text = dateTimeOffset.ToString(DateTimeFormat, CultureInfo.InvariantCulture); + } + + writer.WriteValue(text); + } + + public override object ReadJson(JsonReader reader, Type objectType) + { + if (reader.TokenType != JsonToken.String) + throw new Exception(string.Format("Unexpected token parsing date. Expected String, got {0}.", reader.TokenType)); + + string dateText = reader.Value.ToString(); + + if (objectType == typeof(DateTimeOffset)) + return DateTimeOffset.Parse(dateText, CultureInfo.InvariantCulture, _dateTimeStyles); + + return DateTime.Parse(dateText, CultureInfo.InvariantCulture, _dateTimeStyles); + } + + public override bool CanConvert(Type objectType) + { + return (typeof(DateTime).IsAssignableFrom(objectType) + || typeof(DateTimeOffset).IsAssignableFrom(objectType)); + } + } +} diff --git a/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs new file mode 100644 index 00000000..e74698c0 --- /dev/null +++ b/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Globalization; + +namespace Newtonsoft.Json.Converters +{ + public class JavaScriptDateTimeConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value) + { + long ticks; + + if (value is DateTime) + { + DateTime dateTime = (DateTime)value; + DateTime utcDateTime = dateTime.ToUniversalTime(); + ticks = JavaScriptConvert.ConvertDateTimeToJavaScriptTicks(utcDateTime); + } + else + { + DateTimeOffset dateTimeOffset = (DateTimeOffset)value; + DateTimeOffset utcDateTimeOffset = dateTimeOffset.ToUniversalTime(); + ticks = JavaScriptConvert.ConvertDateTimeToJavaScriptTicks(utcDateTimeOffset.UtcDateTime); + } + + writer.WriteStartConstructor("Date"); + writer.WriteValue(ticks); + writer.WriteEndConstructor(); + } + + public override object ReadJson(JsonReader reader, Type objectType) + { + if (reader.TokenType != JsonToken.StartConstructor || string.Compare(reader.Value.ToString(), "Date", StringComparison.Ordinal) != 0) + throw new Exception(string.Format("Unexpected token or value when parsing date. Token: {0}, Value: {1}", reader.TokenType, reader.Value)); + + reader.Read(); + + if (reader.TokenType != JsonToken.Integer) + throw new Exception(string.Format("Unexpected token parsing date. Expected Integer, got {0}.", reader.TokenType)); + + long ticks = (long)reader.Value; + + DateTime d = JavaScriptConvert.ConvertJavaScriptTicksToDateTime(ticks); + + reader.Read(); + + if (reader.TokenType != JsonToken.EndConstructor) + throw new Exception(string.Format("Unexpected token parsing date. Expected EndConstructor, got {0}.", reader.TokenType)); + + if (objectType == typeof(DateTimeOffset)) + return new DateTimeOffset(d); + + return d; + } + + public override bool CanConvert(Type objectType) + { + return (typeof(DateTime).IsAssignableFrom(objectType) + || typeof(DateTimeOffset).IsAssignableFrom(objectType)); + } + } +} diff --git a/Src/Newtonsoft.Json/Converters/JsonDateTimeSerializationMode.cs b/Src/Newtonsoft.Json/Converters/JsonDateTimeSerializationMode.cs new file mode 100644 index 00000000..4b9f3100 --- /dev/null +++ b/Src/Newtonsoft.Json/Converters/JsonDateTimeSerializationMode.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Newtonsoft.Json.Converters +{ + public enum JsonDateTimeSerializationMode + { + Local, + Utc, + Unspecified, + RoundtripKind + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs index 3c5a7adb..0ff6a1f2 100644 --- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs @@ -29,6 +29,7 @@ using System.Text; using System.Drawing; using System.Xml; using Newtonsoft.Json.Utilities; +using System.Linq; namespace Newtonsoft.Json.Converters { @@ -40,6 +41,7 @@ namespace Newtonsoft.Json.Converters private const string WhitespaceName = "#whitespace"; private const string SignificantWhitespaceName = "#significant-whitespace"; private const string DeclarationName = "?xml"; + private const string JsonNamespaceUri = "http://james.newtonking.com/projects/json"; #region Writing public override void WriteJson(JsonWriter writer, object value) @@ -106,13 +108,29 @@ namespace Newtonsoft.Json.Converters foreach (KeyValuePair> nodeNameGroup in nodesGroupedByName) { List groupedNodes = nodeNameGroup.Value; + bool writeArray; if (groupedNodes.Count == 1) + { + XmlNode singleNode = groupedNodes[0]; + XmlAttribute jsonArrayAttribute = (singleNode.Attributes != null) ? singleNode.Attributes["Array", JsonNamespaceUri] : null; + if (jsonArrayAttribute != null) + writeArray = XmlConvert.ToBoolean(jsonArrayAttribute.Value); + else + writeArray = false; + } + else + { + writeArray = true; + } + + if (!writeArray) { SerializeNode(writer, groupedNodes[0], true); } else { + string elementNames = nodeNameGroup.Key; writer.WritePropertyName(nodeNameGroup.Key); writer.WriteStartArray(); @@ -138,7 +156,7 @@ namespace Newtonsoft.Json.Converters if (writePropertyName) writer.WritePropertyName(node.Name); - if (CollectionUtils.IsNullOrEmpty(node.Attributes) && node.ChildNodes.Count == 1 + if (ValueAttributes(node.Attributes).Count() == 0 && node.ChildNodes.Count == 1 && node.ChildNodes[0].NodeType == XmlNodeType.Text) { // write elements with a single text child as a name value pair @@ -149,6 +167,20 @@ namespace Newtonsoft.Json.Converters // empty element writer.WriteNull(); } + else if (node.ChildNodes.OfType().Where(x => x.Name.StartsWith("-")).Count() > 1) + { + XmlElement constructorValueElement = node.ChildNodes.OfType().Where(x => x.Name.StartsWith("-")).First(); + string constructorName = constructorValueElement.Name.Substring(1); + + writer.WriteStartConstructor(constructorName); + + for (int i = 0; i < node.ChildNodes.Count; i++) + { + SerializeNode(writer, node.ChildNodes[i], false); + } + + writer.WriteEndConstructor(); + } else { writer.WriteStartObject(); @@ -174,6 +206,11 @@ namespace Newtonsoft.Json.Converters case XmlNodeType.ProcessingInstruction: case XmlNodeType.Whitespace: case XmlNodeType.SignificantWhitespace: + if (node.Prefix == "xmlns" && node.Value == JsonNamespaceUri) + break; + else if (node.NamespaceURI == JsonNamespaceUri) + break; + if (writePropertyName) writer.WritePropertyName(GetPropertyName(node)); writer.WriteValue(node.Value); @@ -216,6 +253,10 @@ namespace Newtonsoft.Json.Converters XmlDocument document = new XmlDocument(); XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable); + + if (reader.TokenType != JsonToken.StartObject) + throw new JsonSerializationException("XmlNodeConverter can only convert JSON that begins with an object."); + reader.Read(); DeserializeNode(reader, document, manager, document); @@ -241,7 +282,7 @@ namespace Newtonsoft.Json.Converters break; default: // processing instructions and the xml declaration start with ? - if (propertyName[0] == '?') + if (!string.IsNullOrEmpty(propertyName) && propertyName[0] == '?') { if (propertyName == DeclarationName) { @@ -292,7 +333,8 @@ namespace Newtonsoft.Json.Converters && reader.TokenType != JsonToken.Boolean && reader.TokenType != JsonToken.Integer && reader.TokenType != JsonToken.Float - && reader.TokenType != JsonToken.Date) + && reader.TokenType != JsonToken.Date + && reader.TokenType != JsonToken.StartConstructor) { // read properties until first non-attribute is encountered while (!finishedAttributes && !finishedElement && reader.Read()) @@ -370,7 +412,8 @@ namespace Newtonsoft.Json.Converters } else if (reader.TokenType == JsonToken.Date) { - element.AppendChild(document.CreateTextNode(XmlConvert.ToString((DateTime)reader.Value))); + DateTime d = (DateTime)reader.Value; + element.AppendChild(document.CreateTextNode(XmlConvert.ToString(d, DateTimeUtils.ToSerializationMode(d.Kind)))); } else if (reader.TokenType == JsonToken.Null) { @@ -400,6 +443,9 @@ namespace Newtonsoft.Json.Converters switch (reader.TokenType) { case JsonToken.PropertyName: + if (currentNode.NodeType == XmlNodeType.Document && document.DocumentElement != null) + throw new JsonSerializationException("JSON root object has multiple properties."); + string propertyName = reader.Value.ToString(); reader.Read(); @@ -415,6 +461,14 @@ namespace Newtonsoft.Json.Converters DeserializeValue(reader, document, manager, propertyName, currentNode); } break; + case JsonToken.StartConstructor: + string constructorName = reader.Value.ToString(); + + while (reader.Read() && reader.TokenType != JsonToken.EndConstructor) + { + DeserializeValue(reader, document, manager, "-" + constructorName, currentNode); + } + break; case JsonToken.EndObject: case JsonToken.EndArray: return; @@ -459,6 +513,16 @@ namespace Newtonsoft.Json.Converters else return qualifiedName.Substring(0, colonPosition); } + + private IEnumerable ValueAttributes(XmlAttributeCollection c) + { + return c.OfType().Where(a => a.NamespaceURI != JsonNamespaceUri); + } + + private IEnumerable ValueNodes(XmlNodeList c) + { + return c.OfType().Where(n => n.NamespaceURI != JsonNamespaceUri); + } #endregion public override bool CanConvert(Type valueType) diff --git a/Src/Newtonsoft.Json/JavaScriptConvert.cs b/Src/Newtonsoft.Json/JavaScriptConvert.cs index ea1f7df2..5bb46380 100644 --- a/Src/Newtonsoft.Json/JavaScriptConvert.cs +++ b/Src/Newtonsoft.Json/JavaScriptConvert.cs @@ -75,7 +75,7 @@ namespace Newtonsoft.Json Null = "null"; Undefined = "undefined"; - InitialJavaScriptDateTicks = (new DateTime(1970, 1, 1)).Ticks; + InitialJavaScriptDateTicks = (new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Ticks; MinimumJavaScriptDate = new DateTime(100, 1, 1); } @@ -86,24 +86,53 @@ namespace Newtonsoft.Json /// A Json string representation of the . public static string ToString(DateTime value) { - long javaScriptTicks = ConvertDateTimeToJavaScriptTicks(value); - - return "new Date(" + javaScriptTicks + ")"; + return ToStringInternal(new DateTimeOffset(value), value.Kind); } - internal static long ConvertDateTimeToJavaScriptTicks(DateTime dateTime) + /// + /// Converts the to it's JavaScript string representation. + /// + /// The value to convert. + /// A Json string representation of the . + public static string ToString(DateTimeOffset value) { - if (dateTime < MinimumJavaScriptDate) - dateTime = MinimumJavaScriptDate; + return ToStringInternal(value, DateTimeKind.Local); + } - long javaScriptTicks = (dateTime.Ticks - InitialJavaScriptDateTicks) / (long)10000; + public static string ToStringInternal(DateTimeOffset value, DateTimeKind kind) + { + long javaScriptTicks = ConvertDateTimeToJavaScriptTicks(value); + + string offset; + switch (kind) + { + case DateTimeKind.Local: + case DateTimeKind.Unspecified: + TimeSpan utcOffset = value.Offset; + offset = utcOffset.Hours.ToString("+00;-00") + utcOffset.Minutes.ToString("00;00"); + break; + default: + offset = string.Empty; + break; + } + return @"""\/Date(" + javaScriptTicks.ToString(CultureInfo.InvariantCulture) + offset + @")\/"""; + } + + internal static long ConvertDateTimeToJavaScriptTicks(DateTimeOffset dateTime) + { + DateTimeOffset utcDateTime = dateTime.ToUniversalTime(); + + //if (utcDateTime < MinimumJavaScriptDate) + // utcDateTime = MinimumJavaScriptDate; + + long javaScriptTicks = (utcDateTime.Ticks - InitialJavaScriptDateTicks) / (long)10000; return javaScriptTicks; } internal static DateTime ConvertJavaScriptTicksToDateTime(long javaScriptTicks) { - DateTime dateTime = new DateTime((javaScriptTicks * 10000) + InitialJavaScriptDateTicks); + DateTime dateTime = new DateTime((javaScriptTicks * 10000) + InitialJavaScriptDateTicks, DateTimeKind.Utc); return dateTime; } @@ -328,6 +357,10 @@ namespace Newtonsoft.Json return ToString((decimal)convertible); } } + else if (value is DateTimeOffset) + { + return ToString((DateTimeOffset)value); + } throw new ArgumentException(string.Format("Unsupported type: {0}. Use the JsonSerializer class to get the object's JSON representation.", value.GetType())); } @@ -396,13 +429,29 @@ namespace Newtonsoft.Json return DeserializeObject(value, null); } + /// + /// Deserializes the specified JSON to the given anonymous type. + /// + /// + /// The anonymous type to deserialize to. This can't be specified + /// traditionally and must be infered from the anonymous type passed + /// as a parameter. + /// + /// The object to deserialize. + /// The anonymous type object. + /// The deserialized anonymous type from the JSON string. + public static T DeserializeAnonymousType(string value, T anonymousTypeObject) + { + return (T)DeserializeObject(value, typeof(T)); + } + /// /// Deserializes the specified object to a Json object. /// /// The type of the object to deserialize. /// The object to deserialize. /// Converters to use while deserializing. - /// The deserialized object from the Json string. + /// The deserialized object from the JSON string. public static T DeserializeObject(string value, params JsonConverter[] converters) { return (T)DeserializeObject(value, typeof(T), converters); @@ -438,7 +487,7 @@ namespace Newtonsoft.Json return SerializeObject(node, converter); } - public static XmlNode DeerializeXmlNode(string value) + public static XmlNode DeserializeXmlNode(string value) { XmlNodeConverter converter = new XmlNodeConverter(); diff --git a/Src/Newtonsoft.Json/JsonConverter.cs b/Src/Newtonsoft.Json/JsonConverter.cs index bff625b1..849f9be1 100644 --- a/Src/Newtonsoft.Json/JsonConverter.cs +++ b/Src/Newtonsoft.Json/JsonConverter.cs @@ -40,9 +40,9 @@ namespace Newtonsoft.Json public virtual object ReadJson(JsonReader reader, Type objectType) { - throw new NotImplementedException(string.Format("{0} has not overriden FromJson method.", GetType().Name)); + throw new NotImplementedException(string.Format("{0} has not overriden the ReadJson method.", GetType().Name)); } public abstract bool CanConvert(Type objectType); } -} +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonReader.cs b/Src/Newtonsoft.Json/JsonReader.cs index db7181e6..8421c2db 100644 --- a/Src/Newtonsoft.Json/JsonReader.cs +++ b/Src/Newtonsoft.Json/JsonReader.cs @@ -217,11 +217,54 @@ namespace Newtonsoft.Json ClearCurrentChar(); _currentState = State.PostValue; - _token = JsonToken.String; - _value = _buffer.ToString(); + string text = _buffer.ToString(); _buffer.Position = 0; - _valueType = typeof(string); - _quoteChar = quote; + + if (text.StartsWith("/Date(", StringComparison.Ordinal) && text.EndsWith(")/", StringComparison.Ordinal)) + { + ParseDate(text); + } + else + { + SetToken(JsonToken.String, text); + _quoteChar = quote; + } + } + + 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); + + if (index != -1) + { + kind = DateTimeKind.Local; + value = value.Substring(0, index); + } + + long javaScriptTicks = long.Parse(value); + DateTime utcDateTime = JavaScriptConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks); + 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); } private bool MoveNext() @@ -648,22 +691,22 @@ namespace Newtonsoft.Json _value = constructorName; _valueType = typeof(string); - if (string.CompareOrdinal(constructorName, "Date") == 0) - { - IList parameters = new List(); + //if (string.CompareOrdinal(constructorName, "Date") == 0) + //{ + // IList parameters = new List(); - MoveNext(); - while (ParseValue() && _token != JsonToken.EndConstructor) - { - parameters.Add(_value); - } + // MoveNext(); + // while (ParseValue() && _token != JsonToken.EndConstructor) + // { + // parameters.Add(_value); + // } - long javaScriptTicks = Convert.ToInt64(parameters[0]); + // long javaScriptTicks = Convert.ToInt64(parameters[0]); - DateTime date = JavaScriptConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks); + // DateTime date = JavaScriptConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks); - SetToken(JsonToken.Date, date); - } + // SetToken(JsonToken.Date, date); + //} @@ -754,6 +797,9 @@ namespace Newtonsoft.Json case JsonType.Array: _currentState = State.Array; break; + case JsonType.Constructor: + _currentState = State.Constructor; + break; case JsonType.None: _currentState = State.Finished; break; diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index 55243ed4..359ed715 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -32,6 +32,7 @@ using System.Reflection; using System.ComponentModel; using Newtonsoft.Json.Utilities; using System.Globalization; +using System.Linq; namespace Newtonsoft.Json { @@ -233,11 +234,7 @@ namespace Newtonsoft.Json case JsonToken.EndConstructor: string constructorName = reader.Value.ToString(); - //if (string.CompareOrdinal(constructorName, "Date") == 0) - // value = CreateDate(reader); - //else - //TODO: use objectType plus constructor arguments to instantiate object - value = constructorName; + value = constructorName; break; case JsonToken.Null: case JsonToken.Undefined: @@ -298,12 +295,15 @@ namespace Newtonsoft.Json return targetConverter.ConvertFromInvariantString(valueString); } + if (targetType == typeof(DateTimeOffset) && value is DateTime) + return new DateTimeOffset((DateTime)value); + if (!targetType.IsAssignableFrom(valueType)) throw new InvalidOperationException(string.Format("Cannot convert object of type '{0}' to type '{1}'", value.GetType(), targetType)); return value; } - + return targetConverter.ConvertFrom(null, CultureInfo.InvariantCulture, value); } else @@ -343,7 +343,7 @@ namespace Newtonsoft.Json else mappedName = member.Name; - bool ignored = member.IsDefined(typeof (JsonIgnoreAttribute), true); + bool ignored = member.IsDefined(typeof(JsonIgnoreAttribute), true); bool readable = ReflectionUtils.CanReadMemberValue(member); bool writable = ReflectionUtils.CanSetMemberValue(member); MemberMapping memberMapping = new MemberMapping(mappedName, member, ignored, readable, writable); @@ -409,50 +409,102 @@ namespace Newtonsoft.Json Type elementType = ReflectionUtils.GetListItemType(objectType); IList populatedList = CollectionUtils.CreateAndPopulateList(objectType, delegate(IList list) + { + while (reader.Read()) { - while (reader.Read()) + switch (reader.TokenType) { - switch (reader.TokenType) - { - case JsonToken.EndArray: - return; - case JsonToken.Comment: - break; - default: - object value = GetObject(reader, elementType); + case JsonToken.EndArray: + return; + case JsonToken.Comment: + break; + default: + object value = GetObject(reader, elementType); - list.Add(value); - break; - } + list.Add(value); + break; } + } - throw new JsonSerializationException("Unexpected end when deserializing array."); - }); + throw new JsonSerializationException("Unexpected end when deserializing array."); + }); return populatedList; } private object PopulateObject(JsonReader reader, Type objectType) { - object newObject = Activator.CreateInstance(objectType); + object newObject; - while (reader.Read()) + if (ReflectionUtils.HasDefaultConstructor(objectType)) { - switch (reader.TokenType) + newObject = Activator.CreateInstance(objectType); + + while (reader.Read()) { - case JsonToken.PropertyName: - string memberName = reader.Value.ToString(); + switch (reader.TokenType) + { + case JsonToken.PropertyName: + string memberName = reader.Value.ToString(); - SetObjectMember(reader, newObject, objectType, memberName); - break; - case JsonToken.EndObject: - return newObject; - default: - throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); + SetObjectMember(reader, newObject, objectType, memberName); + break; + case JsonToken.EndObject: + return newObject; + default: + throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); + } } - } - throw new JsonSerializationException("Unexpected end when deserializing object."); + throw new JsonSerializationException("Unexpected end when deserializing object."); + } + else + { + ConstructorInfo c = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).SingleOrDefault(); + + if (c == null) + throw new Exception("sdsdf"); + + IDictionary constructorParameters = c.GetParameters().ToDictionary(p => p, p => (object)null); + //IList constructorValues + + bool exit = false; + while (!exit && reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + string memberName = reader.Value.ToString(); + ParameterInfo matchingConstructorParameter = constructorParameters + .Where(kv => kv.Key.Name == memberName) + .Select(kv => kv.Key) + .SingleOrDefault(); + + if (!reader.Read()) + throw new JsonSerializationException(string.Format("Unexpected end when setting {0}'s value.", memberName)); + + + if (matchingConstructorParameter != null) + { + constructorParameters[matchingConstructorParameter] = GetObject(reader, matchingConstructorParameter.ParameterType); + } + + + + //SetObjectMember(reader, newObject, objectType, memberName); + break; + case JsonToken.EndObject: + exit = true; + break; + default: + throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); + } + } + + newObject = Activator.CreateInstance(objectType, constructorParameters.Values.ToArray()); + + return newObject; + } } #endregion @@ -554,6 +606,10 @@ namespace Newtonsoft.Json break; } } + else if (value is DateTimeOffset) + { + writer.WriteValue((DateTimeOffset)value); + } else if (value is IList) { SerializeList(writer, (IList)value); @@ -694,4 +750,4 @@ namespace Newtonsoft.Json } #endregion } -} +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonWriter.cs b/Src/Newtonsoft.Json/JsonWriter.cs index 953f198b..f488e6bf 100644 --- a/Src/Newtonsoft.Json/JsonWriter.cs +++ b/Src/Newtonsoft.Json/JsonWriter.cs @@ -724,6 +724,15 @@ namespace Newtonsoft.Json { WriteValueInternal(JavaScriptConvert.ToString(value), JsonToken.Date); } + + /// + /// Writes a value. + /// + /// The value to write. + public void WriteValue(DateTimeOffset value) + { + WriteValueInternal(JavaScriptConvert.ToString(value), JsonToken.Date); + } #endregion /// diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 51fb3641..530039ee 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -73,7 +73,9 @@ - + + + @@ -105,6 +107,7 @@ + diff --git a/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs b/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs new file mode 100644 index 00000000..4ba824a7 --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; + +namespace Newtonsoft.Json.Utilities +{ + public static class DateTimeUtils + { + public static string GetLocalOffset(this DateTime d) + { + TimeSpan utcOffset = TimeZoneInfo.Local.GetUtcOffset(d); + + return utcOffset.Hours.ToString("+00;-00") + ":" + utcOffset.Minutes.ToString("00;00"); + } + + public static XmlDateTimeSerializationMode ToSerializationMode(DateTimeKind kind) + { + switch (kind) + { + case DateTimeKind.Local: + return XmlDateTimeSerializationMode.Local; + case DateTimeKind.Unspecified: + return XmlDateTimeSerializationMode.Unspecified; + case DateTimeKind.Utc: + return XmlDateTimeSerializationMode.Utc; + default: + throw new ArgumentOutOfRangeException("kind", kind, "Unexpected DateTimeKind value."); + } + } + } +} diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index c6084c3a..79a21707 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -545,4 +545,4 @@ namespace Newtonsoft.Json.Utilities return Activator.CreateInstance(specificType, args); } } -} +} \ No newline at end of file