From 3109027c75beca09701ade023cc0e5f4a439e380 Mon Sep 17 00:00:00 2001 From: JamesNK Date: Thu, 28 Feb 2008 09:32:09 +0000 Subject: [PATCH] New features: Added support for deserializing to an anonymous type with the DeserializeAnonymousType method. Support for reading, writing and serializing the new DateTimeOffset type. Added IsoDateTimeConverter. Converts DateTimes and DateTimeOffsets to and from the ISO 8601 format. Added JavaScriptDateTimeConverter. Converts DateTimes and DateTimeOffsets to and from a JavaScript date constructor. XmlNodeConverter handles serializing and deserializing JavaScript constructors. Ability to force XmlNodeConverter to write a value in an array. Logic is controlled by an attribute, json:Array="true". Changes: Dates are now serialized in a JSON complient manner, similar to ASP.NET's JavaScriptSerializer or WCF's DataContractJsonSerializer - http://weblogs.asp.net/bleroy/archive/2008/01/18/dates-and-json.aspx Removed the AspNetAjaxDateTimeConverter. --- .../AspNetAjaxDateTimeConverterTest.cs | 70 ---------- .../DateTimeTestClass.cs | 14 ++ .../IsoDateTimeConverterTests.cs | 95 +++++++++++++ .../JavaScriptConvertTest.cs | 3 +- .../JavaScriptDateTimeConverterTests.cs | 57 ++++++++ Src/Newtonsoft.Json.Tests/JsonReaderTest.cs | 3 +- .../JsonSerializerTest.cs | 86 ++++++++++-- Src/Newtonsoft.Json.Tests/JsonWriterTest.cs | 3 +- Src/Newtonsoft.Json.Tests/LinqToJsonTest.cs | 21 +-- .../Newtonsoft.Json.Tests.csproj | 9 +- Src/Newtonsoft.Json.Tests/TestFixtureBase.cs | 19 +++ .../UtcDateTimeConverterTest.cs} | 37 ++--- .../XmlNodeConverterTest.cs | 94 +++++++++++-- .../Converters/IsoDateTimeConverter.cs | 74 ++++++++++ .../Converters/JavaScriptDateTimeConverter.cs | 64 +++++++++ .../JsonDateTimeSerializationMode.cs | 15 +++ .../Converters/XmlNodeConverter.cs | 72 +++++++++- Src/Newtonsoft.Json/JavaScriptConvert.cs | 71 ++++++++-- Src/Newtonsoft.Json/JsonConverter.cs | 4 +- Src/Newtonsoft.Json/JsonReader.cs | 78 ++++++++--- Src/Newtonsoft.Json/JsonSerializer.cs | 126 +++++++++++++----- Src/Newtonsoft.Json/JsonWriter.cs | 9 ++ Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 5 +- .../Utilities/DateTimeUtils.cs | 33 +++++ .../Utilities/ReflectionUtils.cs | 2 +- 25 files changed, 865 insertions(+), 199 deletions(-) delete mode 100644 Src/Newtonsoft.Json.Tests/AspNetAjaxDateTimeConverterTest.cs create mode 100644 Src/Newtonsoft.Json.Tests/DateTimeTestClass.cs create mode 100644 Src/Newtonsoft.Json.Tests/IsoDateTimeConverterTests.cs create mode 100644 Src/Newtonsoft.Json.Tests/JavaScriptDateTimeConverterTests.cs create mode 100644 Src/Newtonsoft.Json.Tests/TestFixtureBase.cs rename Src/{Newtonsoft.Json/Converters/AspNetAjaxDateTimeConverter.cs => Newtonsoft.Json.Tests/UtcDateTimeConverterTest.cs} (57%) create mode 100644 Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs create mode 100644 Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs create mode 100644 Src/Newtonsoft.Json/Converters/JsonDateTimeSerializationMode.cs create mode 100644 Src/Newtonsoft.Json/Utilities/DateTimeUtils.cs 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