diff --git a/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs index 61d226ff..0a3ea691 100644 --- a/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs @@ -762,11 +762,12 @@ keyword such as type of business."" [Test] public void SerializeArrayAsArrayList() { - string jsonText = @"[3, ""somestring"",[1,2,3]]"; + string jsonText = @"[3, ""somestring"",[1,2,3],{}]"; ArrayList o = JsonConvert.DeserializeObject(jsonText); - Assert.AreEqual(3, o.Count); + Assert.AreEqual(4, o.Count); Assert.AreEqual(3, ((JArray)o[2]).Count); + Assert.AreEqual(0, ((JObject)o[3]).Count); } #endif @@ -1617,5 +1618,738 @@ keyword such as type of business."" Assert.AreEqual("_name: Jeff, _age: 26, Rank: 10, JsonTitle: Dr", cc.ToString()); } #endif + + [Test] + public void PopulatePerson() + { + Person p = new Person(); + + JsonSerializer serializer = new JsonSerializer(); + serializer.Populate(new StringReader(@"{""Name"":""James""}"), p); + + Assert.AreEqual("James", p.Name); + } + + [Test] + public void PopulateListOfPeople() + { + List p = new List(); + + JsonSerializer serializer = new JsonSerializer(); + serializer.Populate(new StringReader(@"[{""Name"":""James""},{""Name"":""Jim""}]"), p); + + Assert.AreEqual(2, p.Count); + Assert.AreEqual("James", p[0].Name); + Assert.AreEqual("Jim", p[1].Name); + } + + [Test] + public void PopulateDictionary() + { + Dictionary p = new Dictionary(); + + JsonSerializer serializer = new JsonSerializer(); + serializer.Populate(new StringReader(@"{""Name"":""James""}"), p); + + Assert.AreEqual(1, p.Count); + Assert.AreEqual("James", p["Name"]); + } + + [Test] + public void SerializeEmployeeReference() + { + Employee mikeManager = new Employee + { + Name = "Mike Manager" + }; + Employee joeUser = new Employee + { + Name = "Joe User", + Manager = mikeManager + }; + + List employees = new List + { + mikeManager, + joeUser + }; + + string json = JsonConvert.SerializeObject(employees, Formatting.Indented); + Assert.AreEqual(@"[ + { + ""$id"": ""1"", + ""Name"": ""Mike Manager"", + ""Manager"": null + }, + { + ""$id"": ""2"", + ""Name"": ""Joe User"", + ""Manager"": { + ""$ref"": ""1"" + } + } +]", json); + } + + [Test] + public void DeserializeEmployeeReference() + { + string json = @"[ + { + ""$id"": ""1"", + ""Name"": ""Mike Manager"", + ""Manager"": null + }, + { + ""$id"": ""2"", + ""Name"": ""Joe User"", + ""Manager"": { + ""$ref"": ""1"" + } + } +]"; + + List employees = JsonConvert.DeserializeObject>(json); + + Assert.AreEqual(2, employees.Count); + Assert.AreEqual("Mike Manager", employees[0].Name); + Assert.AreEqual("Joe User", employees[1].Name); + Assert.AreEqual(employees[0], employees[1].Manager); + } + + [Test] + public void SerializeCircularReference() + { + CircularReferenceClass c1 = new CircularReferenceClass {Name = "c1"}; + CircularReferenceClass c2 = new CircularReferenceClass {Name = "c2"}; + CircularReferenceClass c3 = new CircularReferenceClass {Name = "c3"}; + + c1.Child = c2; + c2.Child = c3; + c3.Child = c1; + + string json = JsonConvert.SerializeObject(c1, Formatting.Indented, new JsonSerializerSettings + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects + }); + + Assert.AreEqual(@"{ + ""$id"": ""1"", + ""Name"": ""c1"", + ""Child"": { + ""$id"": ""2"", + ""Name"": ""c2"", + ""Child"": { + ""$id"": ""3"", + ""Name"": ""c3"", + ""Child"": { + ""$ref"": ""1"" + } + } + } +}", json); + } + + [Test] + public void DeserializeCircularReference() + { + string json = @"{ + ""$id"": ""1"", + ""Name"": ""c1"", + ""Child"": { + ""$id"": ""2"", + ""Name"": ""c2"", + ""Child"": { + ""$id"": ""3"", + ""Name"": ""c3"", + ""Child"": { + ""$ref"": ""1"" + } + } + } +}"; + + CircularReferenceClass c1 = + JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects + }); + + Assert.AreEqual("c1", c1.Name); + Assert.AreEqual("c2", c1.Child.Name); + Assert.AreEqual("c3", c1.Child.Child.Name); + Assert.AreEqual("c1", c1.Child.Child.Child.Name); + } + + [Test] + public void SerializeReferenceInList() + { + Employee e1 = new Employee { Name = "e1" }; + Employee e2 = new Employee { Name = "e2" }; + + List employees = new List { e1, e2, e1, e2 }; + + string json = JsonConvert.SerializeObject(employees, Formatting.Indented); + + Assert.AreEqual(@"[ + { + ""$id"": ""1"", + ""Name"": ""e1"", + ""Manager"": null + }, + { + ""$id"": ""2"", + ""Name"": ""e2"", + ""Manager"": null + }, + { + ""$ref"": ""1"" + }, + { + ""$ref"": ""2"" + } +]", json); + } + + [Test] + public void DeserializeReferenceInList() + { + string json = @"[ + { + ""$id"": ""1"", + ""Name"": ""e1"", + ""Manager"": null + }, + { + ""$id"": ""2"", + ""Name"": ""e2"", + ""Manager"": null + }, + { + ""$ref"": ""1"" + }, + { + ""$ref"": ""2"" + } +]"; + + List employees = JsonConvert.DeserializeObject>(json); + Assert.AreEqual(4, employees.Count); + + Assert.AreEqual("e1", employees[0].Name); + Assert.AreEqual("e2", employees[1].Name); + Assert.AreEqual("e1", employees[2].Name); + Assert.AreEqual("e2", employees[3].Name); + + Assert.AreEqual(employees[0], employees[2]); + Assert.AreEqual(employees[1], employees[3]); + } + + [Test] + public void SerializeReferenceInDictionary() + { + Employee e1 = new Employee {Name = "e1"}; + Employee e2 = new Employee {Name = "e2"}; + + Dictionary employees = new Dictionary + { + {"One", e1}, + {"Two", e2}, + {"Three", e1}, + {"Four", e2} + }; + + string json = JsonConvert.SerializeObject(employees, Formatting.Indented); + + Assert.AreEqual(@"{ + ""One"": { + ""$id"": ""1"", + ""Name"": ""e1"", + ""Manager"": null + }, + ""Two"": { + ""$id"": ""2"", + ""Name"": ""e2"", + ""Manager"": null + }, + ""Three"": { + ""$ref"": ""1"" + }, + ""Four"": { + ""$ref"": ""2"" + } +}", json); + } + + [Test] + public void DeserializeReferenceInDictionary() + { + string json = @"{ + ""One"": { + ""$id"": ""1"", + ""Name"": ""e1"", + ""Manager"": null + }, + ""Two"": { + ""$id"": ""2"", + ""Name"": ""e2"", + ""Manager"": null + }, + ""Three"": { + ""$ref"": ""1"" + }, + ""Four"": { + ""$ref"": ""2"" + } +}"; + + Dictionary employees = JsonConvert.DeserializeObject>(json); + Assert.AreEqual(4, employees.Count); + + Employee e1 = employees["One"]; + Employee e2 = employees["Two"]; + + Assert.AreEqual("e1", e1.Name); + Assert.AreEqual("e2", e2.Name); + + Assert.AreEqual(e1, employees["Three"]); + Assert.AreEqual(e2, employees["Four"]); + } + + public class CircularDictionary : Dictionary + { + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "Self referencing loop")] + public void SerializeCircularDictionarysError() + { + CircularDictionary circularDictionary = new CircularDictionary(); + circularDictionary.Add("other", new CircularDictionary { { "blah", null } }); + circularDictionary.Add("self", circularDictionary); + + JsonConvert.SerializeObject(circularDictionary, Formatting.Indented); + } + + [Test] + public void SerializeCircularDictionarysIgnore() + { + CircularDictionary circularDictionary = new CircularDictionary(); + circularDictionary.Add("other", new CircularDictionary { { "blah", null } }); + circularDictionary.Add("self", circularDictionary); + + string json = JsonConvert.SerializeObject(circularDictionary, Formatting.Indented, + new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); + + Assert.AreEqual(@"{ + ""other"": { + ""blah"": null + } +}", json); + } + + [Test] + public void SerializeDictionarysWithPreserveObjectReferences() + { + CircularDictionary circularDictionary = new CircularDictionary(); + circularDictionary.Add("other", new CircularDictionary { {"blah", null }}); + circularDictionary.Add("self", circularDictionary); + + string json = JsonConvert.SerializeObject(circularDictionary, Formatting.Indented, + new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }); + + Assert.AreEqual(@"{ + ""$id"": ""1"", + ""other"": { + ""$id"": ""2"", + ""blah"": null + }, + ""self"": { + ""$ref"": ""1"" + } +}", json); + } + + [Test] + public void DeserializeDictionarysWithPreserveObjectReferences() + { + string json = @"{ + ""$id"": ""1"", + ""other"": { + ""$id"": ""2"", + ""blah"": null + }, + ""self"": { + ""$ref"": ""1"" + } +}"; + + CircularDictionary circularDictionary = JsonConvert.DeserializeObject(json, + new JsonSerializerSettings + { + PreserveReferencesHandling = PreserveReferencesHandling.All + }); + + Assert.AreEqual(2, circularDictionary.Count); + Assert.AreEqual(1, circularDictionary["other"].Count); + Assert.AreEqual(circularDictionary, circularDictionary["self"]); + } + + public class CircularList : List + { + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "Self referencing loop")] + public void SerializeCircularListsError() + { + CircularList circularList = new CircularList(); + circularList.Add(null); + circularList.Add(new CircularList { null }); + circularList.Add(new CircularList { new CircularList { circularList } }); + + JsonConvert.SerializeObject(circularList, Formatting.Indented); + } + + [Test] + public void SerializeCircularListsIgnore() + { + CircularList circularList = new CircularList(); + circularList.Add(null); + circularList.Add(new CircularList { null }); + circularList.Add(new CircularList { new CircularList { circularList } }); + + string json = JsonConvert.SerializeObject(circularList, + Formatting.Indented, + new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); + + Assert.AreEqual(@"[ + null, + [ + null + ], + [ + [] + ] +]", json); + } + + [Test] + public void SerializeListsWithPreserveObjectReferences() + { + CircularList circularList = new CircularList(); + circularList.Add(null); + circularList.Add(new CircularList { null }); + circularList.Add(new CircularList { new CircularList { circularList }}); + + string json = JsonConvert.SerializeObject(circularList, Formatting.Indented, + new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }); + + WriteEscapedJson(json); + Assert.AreEqual(@"{ + ""$id"": ""1"", + ""value"": [ + null, + { + ""$id"": ""2"", + ""value"": [ + null + ] + }, + { + ""$id"": ""3"", + ""value"": [ + { + ""$id"": ""4"", + ""value"": [ + { + ""$ref"": ""1"" + } + ] + } + ] + } + ] +}", json); + } + + [Test] + public void DeserializeListsWithPreserveObjectReferences() + { + string json = @"{ + ""$id"": ""1"", + ""value"": [ + null, + { + ""$id"": ""2"", + ""value"": [ + null + ] + }, + { + ""$id"": ""3"", + ""value"": [ + { + ""$id"": ""4"", + ""value"": [ + { + ""$ref"": ""1"" + } + ] + } + ] + } + ] +}"; + + CircularList circularList = JsonConvert.DeserializeObject(json, + new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }); + + Assert.AreEqual(3, circularList.Count); + Assert.AreEqual(null, circularList[0]); + Assert.AreEqual(1, circularList[1].Count); + Assert.AreEqual(1, circularList[2].Count); + Assert.AreEqual(1, circularList[2][0].Count); + Assert.IsTrue(ReferenceEquals(circularList, circularList[2][0][0])); + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = @"Cannot preserve reference to array or readonly list: System.String[][]")] + public void DeserializeArraysWithPreserveObjectReferences() + { + string json = @"{ + ""$id"": ""1"", + ""value"": [ + null, + { + ""$id"": ""2"", + ""value"": [ + null + ] + }, + { + ""$id"": ""3"", + ""value"": [ + { + ""$id"": ""4"", + ""value"": [ + { + ""$ref"": ""1"" + } + ] + } + ] + } + ] +}"; + + JsonConvert.DeserializeObject(json, + new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }); + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = @"Unexpected end when deserializing object.")] + public void UnexpectedEnd() + { + string json = @"{ + ""$id"":"; + + JsonConvert.DeserializeObject(json, + new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }); + } + + public class DictionaryInterfaceClass + { + public string Name { get; set; } + public IDictionary Dictionary { get; set; } + public ICollection Collection { get; set; } + public Employee Employee { get; set; } + public object Random { get; set; } + + public DictionaryInterfaceClass() + { + Dictionary = new Dictionary + { + { "existing", 1 } + }; + Collection = new List + { + 1, + 2, + 3 + }; + Employee = new Employee + { + Name = "EmployeeName!" + }; + } + } + + [Test] + public void DeserializeDictionaryInterface() + { + string json = @"{ + ""Name"": ""Name!"", + ""Dictionary"": { + ""Item"": 11 + } +}"; + + DictionaryInterfaceClass c = JsonConvert.DeserializeObject(json, + new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }); + Assert.AreEqual("Name!", c.Name); + Assert.AreEqual(1, c.Dictionary.Count); + Assert.AreEqual(11, c.Dictionary["Item"]); + } + + [Test] + public void DeserializeDictionaryInterfaceWithExistingValues() + { + string json = @"{ + ""Random"": { + ""blah"": 1 + }, + ""Name"": ""Name!"", + ""Dictionary"": { + ""Item"": 11, + ""Item1"": 12 + }, + ""Collection"": [ + 999 + ], + ""Employee"": { + ""Manager"": { + ""Name"": ""ManagerName!"" + } + } +}"; + + DictionaryInterfaceClass c = JsonConvert.DeserializeObject(json, + new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Reuse }); + + Assert.AreEqual("Name!", c.Name); + Assert.AreEqual(3, c.Dictionary.Count); + Assert.AreEqual(11, c.Dictionary["Item"]); + Assert.AreEqual(1, c.Dictionary["existing"]); + Assert.AreEqual(4, c.Collection.Count); + Assert.AreEqual(1, c.Collection.ElementAt(0)); + Assert.AreEqual(999, c.Collection.ElementAt(3)); + Assert.AreEqual("EmployeeName!", c.Employee.Name); + Assert.AreEqual("ManagerName!", c.Employee.Manager.Name); + Assert.IsNotNull(c.Random); + } + + public class CircularReferenceClassConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + CircularReferenceClass circularReferenceClass = (CircularReferenceClass) value; + + string reference = serializer.ReferenceResolver.ResolveReference(circularReferenceClass); + + JObject me = new JObject(); + me["$id"] = new JValue(reference); + me["$type"] = new JValue(value.GetType().Name); + me["Name"] = new JValue(circularReferenceClass.Name); + + JObject o = JObject.FromObject(circularReferenceClass.Child, serializer); + me["Child"] = o; + + me.WriteTo(writer); + } + + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) + { + JObject o = JObject.Load(reader); + string id = (string)o["$id"]; + if (id != null) + { + CircularReferenceClass circularReferenceClass = new CircularReferenceClass(); + serializer.Populate(o.CreateReader(), circularReferenceClass); + return circularReferenceClass; + } + else + { + string reference = (string)o["$ref"]; + return serializer.ReferenceResolver.ResolveObject(reference); + } + } + + public override bool CanConvert(Type objectType) + { + return (objectType == typeof(CircularReferenceClass)); + } + } + + [Test] + public void SerializeCircularReferencesWithConverter() + { + CircularReferenceClass c1 = new CircularReferenceClass { Name = "c1" }; + CircularReferenceClass c2 = new CircularReferenceClass { Name = "c2" }; + CircularReferenceClass c3 = new CircularReferenceClass { Name = "c3" }; + + c1.Child = c2; + c2.Child = c3; + c3.Child = c1; + + string json = JsonConvert.SerializeObject(c1, Formatting.Indented, new JsonSerializerSettings + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + Converters = new List { new CircularReferenceClassConverter() } + }); + + Assert.AreEqual(@"{ + ""$id"": ""1"", + ""$type"": ""CircularReferenceClass"", + ""Name"": ""c1"", + ""Child"": { + ""$id"": ""2"", + ""$type"": ""CircularReferenceClass"", + ""Name"": ""c2"", + ""Child"": { + ""$id"": ""3"", + ""$type"": ""CircularReferenceClass"", + ""Name"": ""c3"", + ""Child"": { + ""$ref"": ""1"" + } + } + } +}", json); + } + + [Test] + public void DeserializeCircularReferencesWithConverter() + { + string json = @"{ + ""$id"": ""1"", + ""$type"": ""CircularReferenceClass"", + ""Name"": ""c1"", + ""Child"": { + ""$id"": ""2"", + ""$type"": ""CircularReferenceClass"", + ""Name"": ""c2"", + ""Child"": { + ""$id"": ""3"", + ""$type"": ""CircularReferenceClass"", + ""Name"": ""c3"", + ""Child"": { + ""$ref"": ""1"" + } + } + } +}"; + + CircularReferenceClass c1 = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + Converters = new List { new CircularReferenceClassConverter() } + }); + + Assert.AreEqual("c1", c1.Name); + Assert.AreEqual("c2", c1.Child.Name); + Assert.AreEqual("c3", c1.Child.Child.Name); + Assert.AreEqual("c1", c1.Child.Child.Child.Name); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/LinqToSql/DepartmentConverter.cs b/Src/Newtonsoft.Json.Tests/LinqToSql/DepartmentConverter.cs index 9187cbd8..a6adc942 100644 --- a/Src/Newtonsoft.Json.Tests/LinqToSql/DepartmentConverter.cs +++ b/Src/Newtonsoft.Json.Tests/LinqToSql/DepartmentConverter.cs @@ -8,7 +8,7 @@ namespace Newtonsoft.Json.Tests.LinqToSql { public class DepartmentConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { Department department = (Department)value; @@ -19,7 +19,7 @@ namespace Newtonsoft.Json.Tests.LinqToSql o.WriteTo(writer); } - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { JObject o = JObject.Load(reader); diff --git a/Src/Newtonsoft.Json.Tests/LinqToSql/GuidByteArrayConverter.cs b/Src/Newtonsoft.Json.Tests/LinqToSql/GuidByteArrayConverter.cs index 79fa74bc..e6d4a5f2 100644 --- a/Src/Newtonsoft.Json.Tests/LinqToSql/GuidByteArrayConverter.cs +++ b/Src/Newtonsoft.Json.Tests/LinqToSql/GuidByteArrayConverter.cs @@ -7,13 +7,13 @@ namespace Newtonsoft.Json.Tests.LinqToSql { public class GuidByteArrayConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { Guid guid = (Guid) value; writer.WriteValue(Convert.ToBase64String(guid.ToByteArray())); } - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { string encodedData = (string) reader.Value; byte[] data = Convert.FromBase64String(encodedData); diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj index 288c3b8d..a3b2a106 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj @@ -80,6 +80,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj index 6f8e38b5..741733a7 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj @@ -84,6 +84,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 8e0ed9ef..1420f8b4 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -111,6 +111,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs index 06eecdbe..be102ab9 100644 --- a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs +++ b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs @@ -41,7 +41,12 @@ namespace Newtonsoft.Json.Tests // return utcOffset.Hours.ToString("+00;-00") + utcOffset.Minutes.ToString("00;00"); //} - protected string EscapedJson(string json) + protected void WriteEscapedJson(string json) + { + Console.WriteLine(EscapeJson(json)); + } + + protected string EscapeJson(string json) { return @"@""" + json.Replace(@"""", @"""""") + @""""; } diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/ConverterPrecedenceClassConverter.cs b/Src/Newtonsoft.Json.Tests/TestObjects/ConverterPrecedenceClassConverter.cs index 7f587986..f22f27de 100644 --- a/Src/Newtonsoft.Json.Tests/TestObjects/ConverterPrecedenceClassConverter.cs +++ b/Src/Newtonsoft.Json.Tests/TestObjects/ConverterPrecedenceClassConverter.cs @@ -34,7 +34,7 @@ namespace Newtonsoft.Json.Tests.TestObjects { public abstract string ConverterType { get; } - public override void WriteJson(JsonWriter writer, object value) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { ConverterPrecedenceClass c = (ConverterPrecedenceClass)value; @@ -43,7 +43,7 @@ namespace Newtonsoft.Json.Tests.TestObjects j.WriteTo(writer); } - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { JToken j = JArray.Load(reader); diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/Employee.cs b/Src/Newtonsoft.Json.Tests/TestObjects/Employee.cs new file mode 100644 index 00000000..938dfb54 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/TestObjects/Employee.cs @@ -0,0 +1,39 @@ +#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.Linq; +using System.Text; + +namespace Newtonsoft.Json.Tests.TestObjects +{ + [JsonObject(IsReference = true)] + public class Employee + { + public string Name { get; set; } + public Employee Manager { get; set; } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/BinaryConverter.cs b/Src/Newtonsoft.Json/Converters/BinaryConverter.cs index c62f9487..ddc8402a 100644 --- a/Src/Newtonsoft.Json/Converters/BinaryConverter.cs +++ b/Src/Newtonsoft.Json/Converters/BinaryConverter.cs @@ -1,4 +1,29 @@ -using System; +#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; #if !SILVERLIGHT && !PocketPC using System.Data.Linq; @@ -23,7 +48,7 @@ namespace Newtonsoft.Json.Converters /// /// The to write to. /// The value. - public override void WriteJson(JsonWriter writer, object value) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { @@ -58,7 +83,7 @@ namespace Newtonsoft.Json.Converters /// The to read from. /// Type of the object. /// The object value. - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { Type t = (ReflectionUtils.IsNullableType(objectType)) ? Nullable.GetUnderlyingType(objectType) diff --git a/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs b/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs new file mode 100644 index 00000000..00a2dcdf --- /dev/null +++ b/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs @@ -0,0 +1,57 @@ +#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.Linq; +using System.Text; + +namespace Newtonsoft.Json.Converters +{ + public abstract class CustomCreationConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); + } + + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) + { + T value = Create(); + if (value == null) + throw new JsonSerializationException("No object created."); + + serializer.Populate(reader, value); + return value; + } + + public abstract T Create(); + + public override bool CanConvert(Type objectType) + { + return typeof (T).IsAssignableFrom(objectType); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/HtmlColorConverter.cs b/Src/Newtonsoft.Json/Converters/HtmlColorConverter.cs index 054f3203..de0e6dca 100644 --- a/Src/Newtonsoft.Json/Converters/HtmlColorConverter.cs +++ b/Src/Newtonsoft.Json/Converters/HtmlColorConverter.cs @@ -41,7 +41,7 @@ namespace Newtonsoft.Json.Converters /// /// The to write to. /// The value. - public override void WriteJson(JsonWriter writer, object value) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteValue(ColorTranslator.ToHtml((Color)value)); } @@ -64,7 +64,7 @@ namespace Newtonsoft.Json.Converters /// The to read from. /// Type of the object. /// The object value. - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { throw new NotImplementedException(); } diff --git a/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs index fa834a9c..c9aed913 100644 --- a/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs @@ -53,7 +53,8 @@ namespace Newtonsoft.Json.Converters /// /// The to write to. /// The value. - public override void WriteJson(JsonWriter writer, object value) + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { string text; @@ -89,8 +90,9 @@ namespace Newtonsoft.Json.Converters /// /// The to read from. /// Type of the object. + /// The calling serializer. /// The object value. - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { Type t = (ReflectionUtils.IsNullableType(objectType)) ? Nullable.GetUnderlyingType(objectType) diff --git a/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs index 9ac9588d..91e869bc 100644 --- a/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/JavaScriptDateTimeConverter.cs @@ -17,7 +17,7 @@ namespace Newtonsoft.Json.Converters /// /// The to write to. /// The value. - public override void WriteJson(JsonWriter writer, object value) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { long ticks; @@ -45,7 +45,7 @@ namespace Newtonsoft.Json.Converters /// The to read from. /// Type of the object. /// The object value. - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { Type t = (ReflectionUtils.IsNullableType(objectType)) ? Nullable.GetUnderlyingType(objectType) diff --git a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs index 19ccc60e..e9fc1bf2 100644 --- a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs +++ b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs @@ -1,4 +1,29 @@ -using System; +#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.Linq; using System.Text; @@ -18,7 +43,7 @@ namespace Newtonsoft.Json.Converters /// /// The to write to. /// The value. - public override void WriteJson(JsonWriter writer, object value) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { @@ -41,7 +66,7 @@ namespace Newtonsoft.Json.Converters /// The to read from. /// Type of the object. /// The object value. - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { Type t = (ReflectionUtils.IsNullableType(objectType)) ? Nullable.GetUnderlyingType(objectType) diff --git a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs index 4694adab..e840e008 100644 --- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs @@ -54,7 +54,7 @@ namespace Newtonsoft.Json.Converters /// /// The to write to. /// The value. - public override void WriteJson(JsonWriter writer, object value) + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { XmlNode node = value as XmlNode; @@ -261,7 +261,7 @@ namespace Newtonsoft.Json.Converters /// The to read from. /// Type of the object. /// The object value. - public override object ReadJson(JsonReader reader, Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { // maybe have CanReader and a CanWrite methods so this sort of test wouldn't be necessary if (objectType != typeof(XmlDocument)) diff --git a/Src/Newtonsoft.Json/JsonContainerAttribute.cs b/Src/Newtonsoft.Json/JsonContainerAttribute.cs index bb88c168..bc223656 100644 --- a/Src/Newtonsoft.Json/JsonContainerAttribute.cs +++ b/Src/Newtonsoft.Json/JsonContainerAttribute.cs @@ -40,6 +40,16 @@ namespace Newtonsoft.Json public string Title { get; set; } public string Description { get; set; } + // yuck. can't set nullable properties on an attribute in C# + // have to use this approach to get an unset default state + internal bool? _isReference; + + public bool IsReference + { + get { return _isReference ?? default(bool); } + set { _isReference = value; } + } + /// /// Initializes a new instance of the class. /// diff --git a/Src/Newtonsoft.Json/JsonConverter.cs b/Src/Newtonsoft.Json/JsonConverter.cs index 1bc9f11a..27f356c4 100644 --- a/Src/Newtonsoft.Json/JsonConverter.cs +++ b/Src/Newtonsoft.Json/JsonConverter.cs @@ -40,15 +40,17 @@ namespace Newtonsoft.Json /// /// The to write to. /// The value. - public abstract void WriteJson(JsonWriter writer, object value); + /// The calling serializer. + public abstract void WriteJson(JsonWriter writer, object value, JsonSerializer serializer); /// /// Reads the JSON representation of the object. /// /// The to read from. /// Type of the object. + /// The calling serializer. /// The object value. - public abstract object ReadJson(JsonReader reader, Type objectType); + public abstract object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer); /// /// Determines whether this instance can convert the specified object type. diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index dfa25577..e929f0ef 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -25,17 +25,9 @@ using System; using System.Collections.Generic; -using System.Text; using System.IO; -using System.Collections; -using System.Reflection; -using System.ComponentModel; using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Utilities; -using System.Globalization; -using System.Linq; -using Newtonsoft.Json.Linq; -using System.Collections.ObjectModel; namespace Newtonsoft.Json { @@ -46,6 +38,7 @@ namespace Newtonsoft.Json public class JsonSerializer { #region Properties + private PreserveReferencesHandling _preserveReferencesHandling; private ReferenceLoopHandling _referenceLoopHandling; private MissingMemberHandling _missingMemberHandling; private ObjectCreationHandling _objectCreationHandling; @@ -53,11 +46,42 @@ namespace Newtonsoft.Json private DefaultValueHandling _defaultValueHandling; private JsonConverterCollection _converters; private IMappingResolver _mappingResolver; + private IReferenceResolver _referenceResolver; + + public virtual IReferenceResolver ReferenceResolver + { + get + { + if (_referenceResolver == null) + _referenceResolver = new DefaultReferenceResolver(); + + return _referenceResolver; + } + set + { + if (value == null) + throw new ArgumentNullException("value", "Reference resolver cannot be null."); + + _referenceResolver = value; + } + } + + public virtual PreserveReferencesHandling PreserveReferencesHandling + { + get { return _preserveReferencesHandling; } + set + { + if (value < PreserveReferencesHandling.None || value > PreserveReferencesHandling.All) + throw new ArgumentOutOfRangeException("value"); + + _preserveReferencesHandling = value; + } + } /// /// Get or set how reference loops (e.g. a class referencing itself) is handled. /// - public ReferenceLoopHandling ReferenceLoopHandling + public virtual ReferenceLoopHandling ReferenceLoopHandling { get { return _referenceLoopHandling; } set @@ -72,7 +96,7 @@ namespace Newtonsoft.Json /// /// Get or set how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. /// - public MissingMemberHandling MissingMemberHandling + public virtual MissingMemberHandling MissingMemberHandling { get { return _missingMemberHandling; } set @@ -87,7 +111,7 @@ namespace Newtonsoft.Json /// /// Get or set how null values are handled during serialization and deserialization. /// - public NullValueHandling NullValueHandling + public virtual NullValueHandling NullValueHandling { get { return _nullValueHandling; } set @@ -102,7 +126,7 @@ namespace Newtonsoft.Json /// /// Get or set how null default are handled during serialization and deserialization. /// - public DefaultValueHandling DefaultValueHandling + public virtual DefaultValueHandling DefaultValueHandling { get { return _defaultValueHandling; } set @@ -118,7 +142,7 @@ namespace Newtonsoft.Json /// Gets or sets how objects are created during deserialization. /// /// The object creation handling. - public ObjectCreationHandling ObjectCreationHandling + public virtual ObjectCreationHandling ObjectCreationHandling { get { return _objectCreationHandling; } set @@ -134,7 +158,7 @@ namespace Newtonsoft.Json /// Gets a collection that will be used during serialization. /// /// Collection that will be used during serialization. - public JsonConverterCollection Converters + public virtual JsonConverterCollection Converters { get { @@ -145,7 +169,7 @@ namespace Newtonsoft.Json } } - public IMappingResolver MappingResolver + public virtual IMappingResolver MappingResolver { get { return _mappingResolver; } set { _mappingResolver = value; } @@ -164,7 +188,6 @@ namespace Newtonsoft.Json _objectCreationHandling = JsonSerializerSettings.DefaultObjectCreationHandling; } - /// /// Creates a new instance using the specified objects. /// @@ -179,6 +202,7 @@ namespace Newtonsoft.Json if (!CollectionUtils.IsNullOrEmpty(settings.Converters)) jsonSerializer.Converters.AddRange(settings.Converters); + jsonSerializer.PreserveReferencesHandling = settings.PreserveReferencesHandling; jsonSerializer.ReferenceLoopHandling = settings.ReferenceLoopHandling; jsonSerializer.MissingMemberHandling = settings.MissingMemberHandling; jsonSerializer.ObjectCreationHandling = settings.ObjectCreationHandling; @@ -190,7 +214,25 @@ namespace Newtonsoft.Json return jsonSerializer; } - #region Deserialize + public void Populate(TextReader reader, object target) + { + Populate(new JsonTextReader(reader), target); + } + + public void Populate(JsonReader reader, object target) + { + PopulateInternal(reader, target); + } + + internal virtual void PopulateInternal(JsonReader reader, object target) + { + ValidationUtils.ArgumentNotNull(reader, "reader"); + ValidationUtils.ArgumentNotNull(target, "target"); + + JsonSerializerReader serializerReader = new JsonSerializerReader(this); + serializerReader.Populate(reader, target); + } + /// /// Deserializes the Json structure contained by the specified . /// @@ -201,27 +243,6 @@ namespace Newtonsoft.Json return Deserialize(reader, null); } - /// - /// Deserializes the Json structure contained by the specified - /// into an instance of the specified type. - /// - /// The containing the object. - /// The of object being deserialized. - /// The instance of being deserialized. - public object Deserialize(JsonReader reader, Type objectType) - { - if (reader == null) - throw new ArgumentNullException("reader"); - - if (!reader.Read()) - return null; - - if (objectType != null) - return CreateObject(reader, objectType, null, null); - else - return CreateJToken(reader); - } - /// /// Deserializes the Json structure contained by the specified /// into an instance of the specified type. @@ -234,463 +255,26 @@ namespace Newtonsoft.Json return Deserialize(new JsonTextReader(reader), objectType); } - private JToken CreateJToken(JsonReader reader) + /// + /// Deserializes the Json structure contained by the specified + /// into an instance of the specified type. + /// + /// The containing the object. + /// The of object being deserialized. + /// The instance of being deserialized. + public object Deserialize(JsonReader reader, Type objectType) { - JToken token; - using (JTokenWriter writer = new JTokenWriter()) - { - writer.WriteToken(reader); - token = writer.Token; - } - - return token; + return DeserializeInternal(reader, objectType); } - private bool HasClassConverter(Type objectType, out JsonConverter converter) + internal virtual object DeserializeInternal(JsonReader reader, Type objectType) { - if (objectType == null) - throw new ArgumentNullException("objectType"); + ValidationUtils.ArgumentNotNull(reader, "reader"); - converter = JsonTypeReflector.GetConverter(objectType, objectType); - return (converter != null); + JsonSerializerReader serializerReader = new JsonSerializerReader(this); + return serializerReader.Deserialize(reader, objectType); } - private object CreateObject(JsonReader reader, Type objectType, object existingValue, JsonConverter memberConverter) - { - object value; - JsonConverter converter; - - if (memberConverter != null) - { - return memberConverter.ReadJson(reader, objectType); - } - else if (objectType != null && HasClassConverter(objectType, out converter)) - { - return converter.ReadJson(reader, objectType); - } - else if (objectType != null && HasMatchingConverter(objectType, out converter)) - { - return converter.ReadJson(reader, objectType); - } - else if (objectType == typeof(JsonRaw)) - { - return JsonRaw.Create(reader); - } - else - { - switch (reader.TokenType) - { - // populate a typed object or generic dictionary/array - // depending upon whether an objectType was supplied - case JsonToken.StartObject: - if (objectType == null) - { - value = CreateJToken(reader); - } - else if (CollectionUtils.IsDictionaryType(objectType)) - { - if (existingValue == null) - value = CreateAndPopulateDictionary(reader, objectType); - else - value = PopulateDictionary(CollectionUtils.CreateDictionaryWrapper(existingValue), reader); - } - else - { - if (existingValue == null) - value = CreateAndPopulateObject(reader, objectType); - else - value = PopulateObject(existingValue, reader, objectType); - } - break; - case JsonToken.StartArray: - if (objectType != null) - { - if (existingValue == null) - value = CreateAndPopulateList(reader, objectType); - else - value = PopulateList(CollectionUtils.CreateCollectionWrapper(existingValue), ReflectionUtils.GetCollectionItemType(objectType), reader); - } - else - { - value = CreateJToken(reader); - } - break; - case JsonToken.Integer: - case JsonToken.Float: - case JsonToken.String: - case JsonToken.Boolean: - case JsonToken.Date: - value = EnsureType(reader.Value, objectType); - break; - case JsonToken.StartConstructor: - case JsonToken.EndConstructor: - string constructorName = reader.Value.ToString(); - - value = constructorName; - break; - case JsonToken.Null: - case JsonToken.Undefined: - if (objectType == typeof(DBNull)) - value = DBNull.Value; - else - value = null; - break; - default: - throw new JsonSerializationException("Unexpected token while deserializing object: " + reader.TokenType); - } - } - - return value; - } - - private object EnsureType(object value, Type targetType) - { - // do something about null value when the targetType is a valuetype? - if (value == null) - return null; - - if (targetType == null) - return value; - - Type valueType = value.GetType(); - - // type of value and type of target don't match - // attempt to convert value's type to target's type - if (valueType != targetType) - { - return ConvertUtils.ConvertOrCast(value, CultureInfo.InvariantCulture, targetType); - } - else - { - return value; - } - } - - private JsonMemberMappingCollection GetMemberMappings(Type objectType) - { - ValidationUtils.ArgumentNotNull(objectType, "objectType"); - - if (_mappingResolver != null) - return _mappingResolver.ResolveMappings(objectType); - - return DefaultMappingResolver.Instance.ResolveMappings(objectType); - } - - private void SetObjectMember(JsonReader reader, object target, Type targetType, string memberName) - { - JsonMemberMappingCollection memberMappings = GetMemberMappings(targetType); - - JsonMemberMapping memberMapping; - // attempt exact case match first - // then try match ignoring case - if (memberMappings.TryGetClosestMatchMapping(memberName, out memberMapping)) - { - SetMappingValue(memberMapping, reader, target); - } - else - { - if (_missingMemberHandling == MissingMemberHandling.Error) - throw new JsonSerializationException("Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, targetType.Name)); - - reader.Skip(); - } - } - - private void SetMappingValue(JsonMemberMapping memberMapping, JsonReader reader, object target) - { - if (memberMapping.Ignored) - { - reader.Skip(); - return; - } - - // get the member's underlying type - Type memberType = ReflectionUtils.GetMemberUnderlyingType(memberMapping.Member); - - object currentValue = null; - bool useExistingValue = false; - - if ((_objectCreationHandling == ObjectCreationHandling.Auto || _objectCreationHandling == ObjectCreationHandling.Reuse) - && (reader.TokenType == JsonToken.StartArray || reader.TokenType == JsonToken.StartObject)) - { - currentValue = ReflectionUtils.GetMemberValue(memberMapping.Member, target); - - useExistingValue = (currentValue != null && !memberType.IsArray && !ReflectionUtils.InheritsGenericDefinition(memberType, typeof(ReadOnlyCollection<>))); - } - - if (!memberMapping.Writable && !useExistingValue) - { - reader.Skip(); - return; - } - - object value = CreateObject(reader, memberType, (useExistingValue) ? currentValue : null, JsonTypeReflector.GetConverter(memberMapping.Member, memberType)); - - if (!useExistingValue && ShouldSetMappingValue(memberMapping, value)) - ReflectionUtils.SetMemberValue(memberMapping.Member, target, value); - } - - private bool ShouldSetMappingValue(JsonMemberMapping memberMapping, object value) - { - if (memberMapping.NullValueHandling.GetValueOrDefault(_nullValueHandling) == NullValueHandling.Ignore && value == null) - return false; - - if (memberMapping.DefaultValueHandling.GetValueOrDefault(_defaultValueHandling) == DefaultValueHandling.Ignore && Equals(value, memberMapping.DefaultValue)) - return false; - - if (!memberMapping.Writable) - return false; - - return true; - } - - private object CreateAndPopulateDictionary(JsonReader reader, Type objectType) - { - if (IsTypeGenericDictionaryInterface(objectType)) - { - Type keyType; - Type valueType; - ReflectionUtils.GetDictionaryKeyValueTypes(objectType, out keyType, out valueType); - objectType = ReflectionUtils.MakeGenericType(typeof(Dictionary<,>), keyType, valueType); - } - - IWrappedDictionary dictionary = CollectionUtils.CreateDictionaryWrapper(Activator.CreateInstance(objectType)); - PopulateDictionary(dictionary, reader); - - return dictionary.UnderlyingDictionary; - } - - private IDictionary PopulateDictionary(IWrappedDictionary dictionary, JsonReader reader) - { - Type dictionaryType = dictionary.UnderlyingDictionary.GetType(); - Type dictionaryKeyType = ReflectionUtils.GetDictionaryKeyType(dictionaryType); - Type dictionaryValueType = ReflectionUtils.GetDictionaryValueType(dictionaryType); - - while (reader.Read()) - { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - object keyValue = EnsureType(reader.Value, dictionaryKeyType); - reader.Read(); - - dictionary.Add(keyValue, CreateObject(reader, dictionaryValueType, null, null)); - break; - case JsonToken.EndObject: - return dictionary; - default: - throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); - } - } - - throw new JsonSerializationException("Unexpected end when deserializing object."); - } - - private object CreateAndPopulateList(JsonReader reader, Type objectType) - { - if (IsTypeGenericCollectionInterface(objectType)) - { - Type itemType = ReflectionUtils.GetCollectionItemType(objectType); - objectType = ReflectionUtils.MakeGenericType(typeof(List<>), itemType); - } - - return CollectionUtils.CreateAndPopulateList(objectType, l => PopulateList(l, ReflectionUtils.GetCollectionItemType(objectType), reader)); - } - - private IList PopulateList(IList list, Type listItemType, JsonReader reader) - { - while (reader.Read()) - { - switch (reader.TokenType) - { - case JsonToken.EndArray: - return list; - case JsonToken.Comment: - break; - default: - object value = CreateObject(reader, listItemType, null, null); - - list.Add(value); - break; - } - } - - throw new JsonSerializationException("Unexpected end when deserializing array."); - } - - private bool IsTypeGenericDictionaryInterface(Type type) - { - if (!type.IsGenericType) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof (IDictionary<,>)); - } - - private bool IsTypeGenericCollectionInterface(Type type) - { - if (!type.IsGenericType) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof (IList<>) - || genericDefinition == typeof (ICollection<>) - || genericDefinition == typeof (IEnumerable<>)); - } - - private object CreateAndPopulateObject(JsonReader reader, Type objectType) - { - object newObject; - - if (objectType.IsInterface || objectType.IsAbstract) - throw new JsonSerializationException("Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantated.".FormatWith(CultureInfo.InvariantCulture, objectType)); - - if (ReflectionUtils.HasDefaultConstructor(objectType)) - { - newObject = Activator.CreateInstance(objectType); - - PopulateObject(newObject, reader, objectType); - return newObject; - } - - return CreateObjectFromNonDefaultConstructor(objectType, reader); - } - - private object CreateObjectFromNonDefaultConstructor(Type objectType, JsonReader reader) - { - // object should have a single constructor - ConstructorInfo c = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).SingleOrDefault(); - - if (c == null) - throw new JsonSerializationException("Could not find a public constructor for type {0}.".FormatWith(CultureInfo.InvariantCulture, objectType)); - - // create a dictionary to put retrieved values into - JsonMemberMappingCollection memberMappings = GetMemberMappings(objectType); - IDictionary mappingValues = memberMappings.ToDictionary(kv => kv, kv => (object)null); - - bool exit = false; - while (!exit && reader.Read()) - { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - string memberName = reader.Value.ToString(); - if (!reader.Read()) - throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); - - JsonMemberMapping memberMapping; - // attempt exact case match first - // then try match ignoring case - if (memberMappings.TryGetClosestMatchMapping(memberName, out memberMapping)) - { - if (!memberMapping.Ignored) - { - Type memberType = ReflectionUtils.GetMemberUnderlyingType(memberMapping.Member); - mappingValues[memberMapping] = CreateObject(reader, memberType, null, memberMapping.MemberConverter); - } - } - else - { - if (_missingMemberHandling == MissingMemberHandling.Error) - throw new JsonSerializationException("Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, objectType.Name)); - - reader.Skip(); - } - break; - case JsonToken.EndObject: - exit = true; - break; - default: - throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); - } - } - - IDictionary constructorParameters = c.GetParameters().ToDictionary(p => p, p => (object)null); - IDictionary remainingMappingValues = new Dictionary(); - - foreach (KeyValuePair mappingValue in mappingValues) - { - ParameterInfo matchingConstructorParameter = constructorParameters.ForgivingCaseSensitiveFind(kv => kv.Key.Name, mappingValue.Key.MappingName).Key; - if (matchingConstructorParameter != null) - constructorParameters[matchingConstructorParameter] = mappingValue.Value; - else - remainingMappingValues.Add(mappingValue); - } - - object createdObject = ReflectionUtils.CreateInstance(objectType, constructorParameters.Values.ToArray()); - - // go through unused values and set the newly created object's properties - foreach (KeyValuePair remainingMappingValue in remainingMappingValues) - { - if (ShouldSetMappingValue(remainingMappingValue.Key, remainingMappingValue.Value)) - ReflectionUtils.SetMemberValue(remainingMappingValue.Key.Member, createdObject, remainingMappingValue.Value); - } - - return createdObject; - } - - private object PopulateObject(object newObject, JsonReader reader, Type objectType) - { - JsonMemberMappingCollection memberMappings = GetMemberMappings(objectType); - Dictionary requiredMappings = - memberMappings.Where(m => m.Required).ToDictionary(m => m.MappingName, m => false); - - while (reader.Read()) - { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - string memberName = reader.Value.ToString(); - - if (!reader.Read()) - throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); - - if (reader.TokenType != JsonToken.Null) - SetRequiredMapping(memberName, requiredMappings); - - SetObjectMember(reader, newObject, objectType, memberName); - break; - case JsonToken.EndObject: - foreach (KeyValuePair requiredMapping in requiredMappings) - { - if (!requiredMapping.Value) - throw new JsonSerializationException("Required property '{0}' not found in JSON.".FormatWith(CultureInfo.InvariantCulture, requiredMapping.Key)); - } - return newObject; - default: - throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); - } - } - - throw new JsonSerializationException("Unexpected end when deserializing object."); - } - - private void SetRequiredMapping(string memberName, Dictionary requiredMappings) - { - // first attempt to find exact case match - // then attempt case insensitive match - if (requiredMappings.ContainsKey(memberName)) - { - requiredMappings[memberName] = true; - } - else - { - foreach (KeyValuePair requiredMapping in requiredMappings) - { - if (string.Compare(requiredMapping.Key, requiredMapping.Key, StringComparison.OrdinalIgnoreCase) == 0) - { - requiredMappings[requiredMapping.Key] = true; - break; - } - } - } - } - - #endregion - - #region Serialize /// /// Serializes the specified and writes the Json structure /// to a Stream using the specified . @@ -710,67 +294,37 @@ namespace Newtonsoft.Json /// The to serialize. public void Serialize(JsonWriter jsonWriter, object value) { - if (jsonWriter == null) - throw new ArgumentNullException("jsonWriter"); - - if (value is JToken) - ((JToken)value).WriteTo(jsonWriter, (_converters != null) ? _converters.ToArray() : null); - else - SerializeValue(jsonWriter, value, null); + SerializeInternal(jsonWriter, value); } - - private void SerializeValue(JsonWriter writer, object value, JsonConverter memberConverter) + internal virtual void SerializeInternal(JsonWriter jsonWriter, object value) { - JsonConverter converter; + ValidationUtils.ArgumentNotNull(jsonWriter, "jsonWriter"); - if (value == null) - { - writer.WriteNull(); - } - else if (memberConverter != null) - { - memberConverter.WriteJson(writer, value); - } - else if (HasClassConverter(value.GetType(), out converter)) - { - converter.WriteJson(writer, value); - } - else if (HasMatchingConverter(value.GetType(), out converter)) - { - converter.WriteJson(writer, value); - } - else if (JsonConvert.IsJsonPrimitive(value)) - { - writer.WriteValue(value); - } - else if (value is IList) - { - SerializeList(writer, (IList)value); - } - else if (value is IDictionary) - { - SerializeDictionary(writer, (IDictionary)value); - } - else if (value is ICollection) - { - SerializeCollection(writer, (ICollection)value); - } - else if (value is IEnumerable) - { - SerializeEnumerable(writer, (IEnumerable)value); - } - else if (value is JsonRaw) - { - writer.WriteRawValue(((JsonRaw)value).Content); - } - else - { - SerializeObject(writer, value); - } + JsonSerializerWriter serializerWriter = new JsonSerializerWriter(this); + serializerWriter.Serialize(jsonWriter, value); } - private bool HasMatchingConverter(Type type, out JsonConverter matchingConverter) + internal bool HasClassConverter(Type objectType, out JsonConverter converter) + { + if (objectType == null) + throw new ArgumentNullException("objectType"); + + converter = JsonTypeReflector.GetConverter(objectType, objectType); + return (converter != null); + } + + internal JsonMemberMappingCollection GetMemberMappings(Type objectType) + { + ValidationUtils.ArgumentNotNull(objectType, "objectType"); + + if (_mappingResolver != null) + return _mappingResolver.ResolveMappings(objectType); + + return DefaultMappingResolver.Instance.ResolveMappings(objectType); + } + + internal bool HasMatchingConverter(Type type, out JsonConverter matchingConverter) { return HasMatchingConverter(_converters, type, out matchingConverter); } @@ -797,121 +351,5 @@ namespace Newtonsoft.Json matchingConverter = null; return false; } - - private void WriteMemberInfoProperty(JsonWriter writer, object value, JsonMemberMapping memberMapping) - { - MemberInfo member = memberMapping.Member; - string propertyName = memberMapping.MappingName; - JsonConverter memberConverter = memberMapping.MemberConverter; - object defaultValue = memberMapping.DefaultValue; - - if (!ReflectionUtils.IsIndexedProperty(member)) - { - object memberValue = ReflectionUtils.GetMemberValue(member, value); - - if (memberMapping.NullValueHandling.GetValueOrDefault(_nullValueHandling) == NullValueHandling.Ignore && memberValue == null) - return; - - if (memberMapping.DefaultValueHandling.GetValueOrDefault(_defaultValueHandling) == DefaultValueHandling.Ignore && object.Equals(memberValue, defaultValue)) - return; - - if (writer.SerializeStack.IndexOf(memberValue) != -1) - { - switch (memberMapping.ReferenceLoopHandling.GetValueOrDefault(_referenceLoopHandling)) - { - case ReferenceLoopHandling.Error: - throw new JsonSerializationException("Self referencing loop"); - case ReferenceLoopHandling.Ignore: - // return from method - return; - case ReferenceLoopHandling.Serialize: - // continue - break; - default: - throw new InvalidOperationException("Unexpected ReferenceLoopHandling value: '{0}'".FormatWith(CultureInfo.InvariantCulture, _referenceLoopHandling)); - } - } - - writer.WritePropertyName(propertyName ?? member.Name); - SerializeValue(writer, memberValue, memberConverter); - } - } - - private void SerializeObject(JsonWriter writer, object value) - { - Type objectType = value.GetType(); - -#if !SILVERLIGHT && !PocketPC - TypeConverter converter = TypeDescriptor.GetConverter(objectType); - - // use the objectType's TypeConverter if it has one and can convert to a string - if (converter != null && !(converter is ComponentConverter) && (converter.GetType() != typeof(TypeConverter) || value is Type)) - { - if (converter.CanConvertTo(typeof(string))) - { - writer.WriteValue(converter.ConvertToInvariantString(value)); - return; - } - } -#else - if (value is Guid || value is Type) - { - writer.WriteValue(value.ToString()); - return; - } -#endif - - writer.SerializeStack.Add(value); - - writer.WriteStartObject(); - - JsonMemberMappingCollection memberMappings = GetMemberMappings(objectType); - - foreach (JsonMemberMapping memberMapping in memberMappings) - { - if (!memberMapping.Ignored && memberMapping.Readable) - WriteMemberInfoProperty(writer, value, memberMapping); - } - - writer.WriteEndObject(); - - writer.SerializeStack.RemoveAt(writer.SerializeStack.Count - 1); - } - - private void SerializeEnumerable(JsonWriter writer, IEnumerable values) - { - SerializeList(writer, values.Cast().ToList()); - } - - private void SerializeCollection(JsonWriter writer, ICollection values) - { - SerializeList(writer, values.Cast().ToList()); - } - - private void SerializeList(JsonWriter writer, IList values) - { - writer.WriteStartArray(); - - for (int i = 0; i < values.Count; i++) - { - SerializeValue(writer, values[i], null); - } - - writer.WriteEndArray(); - } - - private void SerializeDictionary(JsonWriter writer, IDictionary values) - { - writer.WriteStartObject(); - - foreach (DictionaryEntry entry in values) - { - writer.WritePropertyName(entry.Key.ToString()); - SerializeValue(writer, entry.Value, null); - } - - writer.WriteEndObject(); - } - #endregion } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonSerializerSettings.cs b/Src/Newtonsoft.Json/JsonSerializerSettings.cs index 7d23f010..be76682b 100644 --- a/Src/Newtonsoft.Json/JsonSerializerSettings.cs +++ b/Src/Newtonsoft.Json/JsonSerializerSettings.cs @@ -17,6 +17,7 @@ namespace Newtonsoft.Json internal const NullValueHandling DefaultNullValueHandling = NullValueHandling.Include; internal const DefaultValueHandling DefaultDefaultValueHandling = DefaultValueHandling.Include; internal const ObjectCreationHandling DefaultObjectCreationHandling = ObjectCreationHandling.Auto; + internal const PreserveReferencesHandling DefaultPreserveReferencesHandling = PreserveReferencesHandling.None; /// /// Gets or sets how reference loops (e.g. a class referencing itself) is handled. @@ -49,6 +50,7 @@ namespace Newtonsoft.Json /// The converters. public IList Converters { get; set; } public IMappingResolver MappingResolver { get; set; } + public PreserveReferencesHandling PreserveReferencesHandling { get; set; } /// /// Initializes a new instance of the class. @@ -60,6 +62,7 @@ namespace Newtonsoft.Json ObjectCreationHandling = DefaultObjectCreationHandling; NullValueHandling = DefaultNullValueHandling; DefaultValueHandling = DefaultDefaultValueHandling; + PreserveReferencesHandling = DefaultPreserveReferencesHandling; Converters = new List(); } } diff --git a/Src/Newtonsoft.Json/JsonWriter.cs b/Src/Newtonsoft.Json/JsonWriter.cs index 99111027..b03078d5 100644 --- a/Src/Newtonsoft.Json/JsonWriter.cs +++ b/Src/Newtonsoft.Json/JsonWriter.cs @@ -121,21 +121,9 @@ namespace Newtonsoft.Json private int _top; - private List _stack; + private readonly List _stack; private State _currentState; private Formatting _formatting; - private List _serializeStack; - - internal List SerializeStack - { - get - { - if (_serializeStack == null) - _serializeStack = new List(); - - return _serializeStack; - } - } /// /// Gets the top. diff --git a/Src/Newtonsoft.Json/Linq/JArray.cs b/Src/Newtonsoft.Json/Linq/JArray.cs index d233055a..726550aa 100644 --- a/Src/Newtonsoft.Json/Linq/JArray.cs +++ b/Src/Newtonsoft.Json/Linq/JArray.cs @@ -138,6 +138,7 @@ namespace Newtonsoft.Json.Linq throw new ArgumentException("An item of type {0} cannot be added to content.".FormatWith(CultureInfo.InvariantCulture, o.Type)); } + /// /// Creates a from an object. /// @@ -145,7 +146,12 @@ namespace Newtonsoft.Json.Linq /// A with the values of the specified object public static new JArray FromObject(object o) { - JToken token = FromObjectInternal(o); + return FromObject(o, new JsonSerializer()); + } + + public static new JArray FromObject(object o, JsonSerializer jsonSerializer) + { + JToken token = FromObjectInternal(o, jsonSerializer); if (token.Type != JTokenType.Array) throw new ArgumentException("Object serialized to {0}. JArray instance expected.".FormatWith(CultureInfo.InvariantCulture, token.Type)); diff --git a/Src/Newtonsoft.Json/Linq/JObject.cs b/Src/Newtonsoft.Json/Linq/JObject.cs index 8d1c156f..2a4d5006 100644 --- a/Src/Newtonsoft.Json/Linq/JObject.cs +++ b/Src/Newtonsoft.Json/Linq/JObject.cs @@ -219,6 +219,7 @@ namespace Newtonsoft.Json.Linq return Load(jsonReader); } + /// /// Creates a from an object. /// @@ -226,14 +227,20 @@ namespace Newtonsoft.Json.Linq /// A with the values of the specified object public static new JObject FromObject(object o) { - JToken token = FromObjectInternal(o); + return FromObject(o, new JsonSerializer()); + } - if (token.Type != JTokenType.Object) + public static new JObject FromObject(object o, JsonSerializer jsonSerializer) + { + JToken token = FromObjectInternal(o, jsonSerializer); + + if (token != null && token.Type != JTokenType.Object) throw new ArgumentException("Object serialized to {0}. JObject instance expected.".FormatWith(CultureInfo.InvariantCulture, token.Type)); return (JObject)token; } + internal override void ValidateObject(JToken o, JToken previous) { ValidationUtils.ArgumentNotNull(o, "o"); diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index e5395907..432d71cb 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -760,11 +760,10 @@ namespace Newtonsoft.Json.Linq return new JTokenReader(this); } - internal static JToken FromObjectInternal(object o) + internal static JToken FromObjectInternal(object o, JsonSerializer jsonSerializer) { ValidationUtils.ArgumentNotNull(o, "o"); - - JsonSerializer jsonSerializer = new JsonSerializer(); + ValidationUtils.ArgumentNotNull(jsonSerializer, "jsonSerializer"); JToken token; using (JTokenWriter jsonWriter = new JTokenWriter()) @@ -783,7 +782,12 @@ namespace Newtonsoft.Json.Linq /// A with the value of the specified object public static JToken FromObject(object o) { - return FromObjectInternal(o); + return FromObjectInternal(o, new JsonSerializer()); + } + + public static JToken FromObject(object o, JsonSerializer jsonSerializer) + { + return FromObjectInternal(o, jsonSerializer); } public static JToken ReadFrom(JsonReader reader) diff --git a/Src/Newtonsoft.Json/Linq/JValue.cs b/Src/Newtonsoft.Json/Linq/JValue.cs index a9ab3e81..d564efe1 100644 --- a/Src/Newtonsoft.Json/Linq/JValue.cs +++ b/Src/Newtonsoft.Json/Linq/JValue.cs @@ -269,7 +269,7 @@ namespace Newtonsoft.Json.Linq JsonConverter matchingConverter; if (value != null && JsonSerializer.HasMatchingConverter(converters, value.GetType(), out matchingConverter)) - matchingConverter.WriteJson(writer, value); + matchingConverter.WriteJson(writer, value, new JsonSerializer()); else defaultWrite(value); } diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj index 9be6a4f8..94b464d8 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj @@ -56,6 +56,7 @@ + @@ -100,6 +101,7 @@ + @@ -113,7 +115,9 @@ + + @@ -125,7 +129,11 @@ + + + + @@ -137,12 +145,12 @@ - + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj index d2ec8c66..28b67180 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj @@ -50,6 +50,7 @@ + @@ -98,13 +99,19 @@ + + - + + + + + @@ -118,6 +125,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 5bd4a071..1ff9fe9b 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -84,7 +84,10 @@ + + + @@ -133,6 +136,7 @@ + @@ -149,10 +153,14 @@ - + + + + + @@ -164,7 +172,7 @@ - + diff --git a/Src/Newtonsoft.Json/PreserveReferencesHandling.cs b/Src/Newtonsoft.Json/PreserveReferencesHandling.cs new file mode 100644 index 00000000..afc55cca --- /dev/null +++ b/Src/Newtonsoft.Json/PreserveReferencesHandling.cs @@ -0,0 +1,40 @@ +#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; + +namespace Newtonsoft.Json +{ + [Flags] + public enum PreserveReferencesHandling + { + None = 0, + Objects = 1, + Arrays = 2, + All = Objects | Arrays + } +} diff --git a/Src/Newtonsoft.Json/Serialization/CachedAttributeGetter.cs b/Src/Newtonsoft.Json/Serialization/CachedAttributeGetter.cs index 878f6299..38bc2a63 100644 --- a/Src/Newtonsoft.Json/Serialization/CachedAttributeGetter.cs +++ b/Src/Newtonsoft.Json/Serialization/CachedAttributeGetter.cs @@ -1,4 +1,29 @@ -using System; +#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.Linq; using System.Reflection; diff --git a/Src/Newtonsoft.Json/Serialization/DefaultMappingResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultMappingResolver.cs index ebf8b3f2..f1b55993 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultMappingResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultMappingResolver.cs @@ -120,6 +120,8 @@ namespace Newtonsoft.Json.Serialization protected virtual JsonMemberMapping CreateMemberMapping(MemberSerialization memberSerialization, MemberInfo member) { + JsonObjectAttribute jsonObjectAttribute = JsonTypeReflector.GetJsonContainerAttribute(member.DeclaringType) as JsonObjectAttribute; + #if !PocketPC DataContractAttribute dataContractAttribute = JsonTypeReflector.GetDataContractAttribute(member.DeclaringType); @@ -174,8 +176,14 @@ namespace Newtonsoft.Json.Serialization NullValueHandling? nullValueHandling = (propertyAttribute != null) ? propertyAttribute._nullValueHandling : null; DefaultValueHandling? defaultValueHandling = (propertyAttribute != null) ? propertyAttribute._defaultValueHandling : null; ReferenceLoopHandling? referenceLoopHandling = (propertyAttribute != null) ? propertyAttribute._referenceLoopHandling : null; + + bool isReference; + if (jsonObjectAttribute != null) + isReference = jsonObjectAttribute.IsReference; + else + isReference = false; - return new JsonMemberMapping(resolvedMappedName, member, ignored, readable, writable, memberConverter, defaultValue, required, nullValueHandling, defaultValueHandling, referenceLoopHandling); + return new JsonMemberMapping(resolvedMappedName, member, ignored, readable, writable, memberConverter, defaultValue, required, isReference, nullValueHandling, defaultValueHandling, referenceLoopHandling); } protected virtual string ResolveMappingName(string mappedName) diff --git a/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs new file mode 100644 index 00000000..812278f4 --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/DefaultReferenceResolver.cs @@ -0,0 +1,82 @@ +#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.Linq; +using System.Text; +using Newtonsoft.Json.Utilities; +using System.Globalization; + +namespace Newtonsoft.Json.Serialization +{ + internal class DefaultReferenceResolver : IReferenceResolver + { + private int _referenceCount; + private BidirectionalDictionary _mappings; + + private BidirectionalDictionary Mappings + { + get + { + if (_mappings == null) + _mappings = new BidirectionalDictionary(); + + return _mappings; + } + } + + public object ResolveObject(string reference) + { + object value; + Mappings.TryGetByFirst(reference, out value); + return value; + } + + public string ResolveReference(object value) + { + string reference; + if (!Mappings.TryGetBySecond(value, out reference)) + { + _referenceCount++; + reference = _referenceCount.ToString(CultureInfo.InvariantCulture); + Mappings.Add(reference, value); + } + + return reference; + } + + public void AddObjectReference(string reference, object value) + { + Mappings.Add(reference, value); + } + + public bool HasObjectReference(object value) + { + string reference; + return Mappings.TryGetBySecond(value, out reference); + } + } +} diff --git a/Src/Newtonsoft.Json/Serialization/IReferenceResolver.cs b/Src/Newtonsoft.Json/Serialization/IReferenceResolver.cs new file mode 100644 index 00000000..81bf8df9 --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/IReferenceResolver.cs @@ -0,0 +1,35 @@ +#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 + +namespace Newtonsoft.Json.Serialization +{ + public interface IReferenceResolver + { + object ResolveObject(string reference); + string ResolveReference(object value); + bool HasObjectReference(object value); + void AddObjectReference(string reference, object value); + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonMemberMapping.cs b/Src/Newtonsoft.Json/Serialization/JsonMemberMapping.cs index a0c6e495..221d068b 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonMemberMapping.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonMemberMapping.cs @@ -40,11 +40,12 @@ namespace Newtonsoft.Json.Serialization private readonly JsonConverter _memberConverter; private readonly object _defaultValue; private readonly bool _required; + private readonly bool _isReference; private readonly NullValueHandling? _nullValueHandling; private readonly DefaultValueHandling? _defaultValueHandling; private readonly ReferenceLoopHandling? _referenceLoopHandling; - public JsonMemberMapping(string mappingName, MemberInfo member, bool ignored, bool readable, bool writable, JsonConverter memberConverter, object defaultValue, bool required, NullValueHandling? nullValueHandling, DefaultValueHandling? defaultValueHandling, ReferenceLoopHandling? referenceLoopHandling) + public JsonMemberMapping(string mappingName, MemberInfo member, bool ignored, bool readable, bool writable, JsonConverter memberConverter, object defaultValue, bool required, bool isReference, NullValueHandling? nullValueHandling, DefaultValueHandling? defaultValueHandling, ReferenceLoopHandling? referenceLoopHandling) { _mappingName = mappingName; _member = member; @@ -54,6 +55,7 @@ namespace Newtonsoft.Json.Serialization _memberConverter = memberConverter; _defaultValue = defaultValue; _required = required; + _isReference = isReference; _nullValueHandling = nullValueHandling; _defaultValueHandling = defaultValueHandling; _referenceLoopHandling = referenceLoopHandling; @@ -92,11 +94,16 @@ namespace Newtonsoft.Json.Serialization public object DefaultValue { get { return _defaultValue; } - } + } public bool Required { - get { return _required; } + get { return _required; } + } + + public bool IsReference + { + get { return _isReference; } } public NullValueHandling? NullValueHandling diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs new file mode 100644 index 00000000..dae47454 --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs @@ -0,0 +1,130 @@ +#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 Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Serialization +{ + internal class JsonSerializerProxy : JsonSerializer + { + private readonly JsonSerializerReader _serializerReader; + private readonly JsonSerializerWriter _serializerWriter; + private readonly JsonSerializer _serializer; + + public override IReferenceResolver ReferenceResolver + { + get { return _serializer.ReferenceResolver; } + set { _serializer.ReferenceResolver = value; } + } + + public override JsonConverterCollection Converters + { + get { return _serializer.Converters; } + } + + public override DefaultValueHandling DefaultValueHandling + { + get { return _serializer.DefaultValueHandling; } + set { _serializer.DefaultValueHandling = value; } + } + + public override IMappingResolver MappingResolver + { + get { return _serializer.MappingResolver; } + set { _serializer.MappingResolver = value; } + } + + public override MissingMemberHandling MissingMemberHandling + { + get { return _serializer.MissingMemberHandling; } + set { _serializer.MissingMemberHandling = value; } + } + + public override NullValueHandling NullValueHandling + { + get { return _serializer.NullValueHandling; } + set { _serializer.NullValueHandling = value; } + } + + public override ObjectCreationHandling ObjectCreationHandling + { + get { return _serializer.ObjectCreationHandling; } + set { _serializer.ObjectCreationHandling = value; } + } + + public override ReferenceLoopHandling ReferenceLoopHandling + { + get { return _serializer.ReferenceLoopHandling; } + set { _serializer.ReferenceLoopHandling = value; } + } + + public override PreserveReferencesHandling PreserveReferencesHandling + { + get { return _serializer.PreserveReferencesHandling; } + set { _serializer.PreserveReferencesHandling = value; } + } + + public JsonSerializerProxy(JsonSerializerReader serializerReader) + { + ValidationUtils.ArgumentNotNull(serializerReader, "serializerReader"); + + _serializerReader = serializerReader; + _serializer = serializerReader._serializer; + } + + public JsonSerializerProxy(JsonSerializerWriter serializerWriter) + { + ValidationUtils.ArgumentNotNull(serializerWriter, "serializerWriter"); + + _serializerWriter = serializerWriter; + _serializer = serializerWriter._serializer; + } + + internal override object DeserializeInternal(JsonReader reader, Type objectType) + { + if (_serializerReader != null) + return _serializerReader.Deserialize(reader, objectType); + else + return _serializer.Deserialize(reader, objectType); + } + + internal override void PopulateInternal(JsonReader reader, object target) + { + if (_serializerReader != null) + _serializerReader.Populate(reader, target); + else + _serializer.Populate(reader, target); + } + + internal override void SerializeInternal(JsonWriter jsonWriter, object value) + { + if (_serializerWriter != null) + _serializerWriter.Serialize(jsonWriter, value); + else + _serializer.Serialize(jsonWriter, value); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerReader.cs new file mode 100644 index 00000000..c5077614 --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerReader.cs @@ -0,0 +1,615 @@ +#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; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Serialization +{ + internal class JsonSerializerReader + { + internal readonly JsonSerializer _serializer; + private JsonSerializerProxy _internalSerializer; + + public JsonSerializerReader(JsonSerializer serializer) + { + ValidationUtils.ArgumentNotNull(serializer, "serializer"); + + _serializer = serializer; + } + + public void Populate(JsonReader reader, object target) + { + ValidationUtils.ArgumentNotNull(target, "target"); + + Type objectType = target.GetType(); + + if (reader.TokenType == JsonToken.None) + reader.Read(); + + if (reader.TokenType == JsonToken.StartArray) + { + PopulateList(CollectionUtils.CreateCollectionWrapper(target), ReflectionUtils.GetCollectionItemType(objectType), reader, null); + } + else if (reader.TokenType == JsonToken.StartObject) + { + if (!reader.Read()) + throw new JsonSerializationException("Unexpected end when deserializing object."); + + if (CollectionUtils.IsDictionaryType(objectType)) + PopulateDictionary(CollectionUtils.CreateDictionaryWrapper(target), reader); + else + PopulateObject(target, reader, objectType); + } + } + + public object Deserialize(JsonReader reader, Type objectType) + { + if (reader == null) + throw new ArgumentNullException("reader"); + + if (!reader.Read()) + return null; + + if (objectType != null) + return CreateValue(reader, objectType, null, null); + else + return CreateJToken(reader); + } + + private JsonSerializerProxy GetInternalSerializer() + { + if (_internalSerializer == null) + _internalSerializer = new JsonSerializerProxy(this); + + return _internalSerializer; + } + + private JToken CreateJToken(JsonReader reader) + { + JToken token; + using (JTokenWriter writer = new JTokenWriter()) + { + writer.WriteToken(reader); + token = writer.Token; + } + + return token; + } + + private object CreateValue(JsonReader reader, Type objectType, object existingValue, JsonConverter memberConverter) + { + object value; + JsonConverter converter; + + if (memberConverter != null) + { + return memberConverter.ReadJson(reader, objectType, GetInternalSerializer()); + } + else if (objectType != null && _serializer.HasClassConverter(objectType, out converter)) + { + return converter.ReadJson(reader, objectType, GetInternalSerializer()); + } + else if (objectType != null && _serializer.HasMatchingConverter(objectType, out converter)) + { + return converter.ReadJson(reader, objectType, GetInternalSerializer()); + } + else if (objectType == typeof(JsonRaw)) + { + return JsonRaw.Create(reader); + } + else + { + switch (reader.TokenType) + { + // populate a typed object or generic dictionary/array + // depending upon whether an objectType was supplied + case JsonToken.StartObject: + if (objectType == null) + { + value = CreateJToken(reader); + } + else + { + CheckedRead(reader); + + if (reader.TokenType == JsonToken.PropertyName) + { + string propertyName = reader.Value.ToString(); + + if (string.Equals(propertyName, JsonTypeReflector.RefPropertyName, StringComparison.Ordinal)) + { + CheckedRead(reader); + string id = reader.Value.ToString(); + + CheckedRead(reader); + return _serializer.ReferenceResolver.ResolveObject(id); + } + else if (string.Equals(propertyName, JsonTypeReflector.IdPropertyName, StringComparison.Ordinal) + && !CollectionUtils.IsDictionaryType(objectType) + && CollectionUtils.IsListType(objectType)) + { + return CreateReferencedList(reader, objectType, existingValue); + } + } + + if (CollectionUtils.IsDictionaryType(objectType)) + { + if (existingValue == null) + value = CreateAndPopulateDictionary(reader, objectType); + else + value = PopulateDictionary(CollectionUtils.CreateDictionaryWrapper(existingValue), reader); + } + else + { + if (existingValue == null) + value = CreateAndPopulateObject(reader, objectType); + else + value = PopulateObject(existingValue, reader, objectType); + } + } + break; + case JsonToken.StartArray: + value = CreateList(reader, objectType, existingValue, null); + break; + case JsonToken.Integer: + case JsonToken.Float: + case JsonToken.String: + case JsonToken.Boolean: + case JsonToken.Date: + value = EnsureType(reader.Value, objectType); + break; + case JsonToken.StartConstructor: + case JsonToken.EndConstructor: + string constructorName = reader.Value.ToString(); + + value = constructorName; + break; + case JsonToken.Null: + case JsonToken.Undefined: + if (objectType == typeof(DBNull)) + value = DBNull.Value; + else + value = null; + break; + default: + throw new JsonSerializationException("Unexpected token while deserializing object: " + reader.TokenType); + } + } + + return value; + } + + private void CheckedRead(JsonReader reader) + { + if (!reader.Read()) + throw new JsonSerializationException("Unexpected end when deserializing object."); + } + + private object CreateReferencedList(JsonReader reader, Type objectType, object existingValue) + { + object value; + + CheckedRead(reader); + string id = reader.Value.ToString(); + + CheckedRead(reader); + if (reader.TokenType != JsonToken.PropertyName && reader.Value.ToString() != "value") + throw new JsonSerializationException("Error reading referenced list."); + + CheckedRead(reader); + if (reader.TokenType != JsonToken.StartArray) + throw new JsonSerializationException("Error reading referenced list."); + + value = CreateList(reader, objectType, existingValue, id); + + CheckedRead(reader); + return value; + } + + private object CreateList(JsonReader reader, Type objectType, object existingValue, string reference) + { + object value; + if (objectType != null) + { + if (existingValue == null) + value = CreateAndPopulateList(reader, objectType, reference); + else + value = PopulateList(CollectionUtils.CreateCollectionWrapper(existingValue), ReflectionUtils.GetCollectionItemType(objectType), reader, reference); + } + else + { + value = CreateJToken(reader); + } + return value; + } + + private object EnsureType(object value, Type targetType) + { + // do something about null value when the targetType is a valuetype? + if (value == null) + return null; + + if (targetType == null) + return value; + + Type valueType = value.GetType(); + + // type of value and type of target don't match + // attempt to convert value's type to target's type + if (valueType != targetType) + { + return ConvertUtils.ConvertOrCast(value, CultureInfo.InvariantCulture, targetType); + } + else + { + return value; + } + } + + private void SetObjectMember(JsonReader reader, object target, Type targetType, string memberName) + { + JsonMemberMappingCollection memberMappings = _serializer.GetMemberMappings(targetType); + + JsonMemberMapping memberMapping; + // attempt exact case match first + // then try match ignoring case + if (memberMappings.TryGetClosestMatchMapping(memberName, out memberMapping)) + { + SetMappingValue(memberMapping, reader, target); + } + else + { + if (_serializer.MissingMemberHandling == MissingMemberHandling.Error) + throw new JsonSerializationException("Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, targetType.Name)); + + reader.Skip(); + } + } + + private void SetMappingValue(JsonMemberMapping memberMapping, JsonReader reader, object target) + { + if (memberMapping.Ignored) + { + reader.Skip(); + return; + } + + // get the member's underlying type + Type memberType = ReflectionUtils.GetMemberUnderlyingType(memberMapping.Member); + + object currentValue = null; + bool useExistingValue = false; + + if ((_serializer.ObjectCreationHandling == ObjectCreationHandling.Auto || _serializer.ObjectCreationHandling == ObjectCreationHandling.Reuse) + && (reader.TokenType == JsonToken.StartArray || reader.TokenType == JsonToken.StartObject)) + { + currentValue = ReflectionUtils.GetMemberValue(memberMapping.Member, target); + + useExistingValue = (currentValue != null && !memberType.IsArray && !ReflectionUtils.InheritsGenericDefinition(memberType, typeof(ReadOnlyCollection<>))); + } + + if (!memberMapping.Writable && !useExistingValue) + { + reader.Skip(); + return; + } + + object value = CreateValue(reader, memberType, (useExistingValue) ? currentValue : null, JsonTypeReflector.GetConverter(memberMapping.Member, memberType)); + + if (!useExistingValue && ShouldSetMappingValue(memberMapping, value)) + ReflectionUtils.SetMemberValue(memberMapping.Member, target, value); + } + + private bool ShouldSetMappingValue(JsonMemberMapping memberMapping, object value) + { + if (memberMapping.NullValueHandling.GetValueOrDefault(_serializer.NullValueHandling) == NullValueHandling.Ignore && value == null) + return false; + + if (memberMapping.DefaultValueHandling.GetValueOrDefault(_serializer.DefaultValueHandling) == DefaultValueHandling.Ignore && Equals(value, memberMapping.DefaultValue)) + return false; + + if (!memberMapping.Writable) + return false; + + return true; + } + + private object CreateAndPopulateDictionary(JsonReader reader, Type objectType) + { + if (IsTypeGenericDictionaryInterface(objectType)) + { + Type keyType; + Type valueType; + ReflectionUtils.GetDictionaryKeyValueTypes(objectType, out keyType, out valueType); + objectType = ReflectionUtils.MakeGenericType(typeof(Dictionary<,>), keyType, valueType); + } + + IWrappedDictionary dictionary = CollectionUtils.CreateDictionaryWrapper(Activator.CreateInstance(objectType)); + PopulateDictionary(dictionary, reader); + + return dictionary.UnderlyingDictionary; + } + + private IDictionary PopulateDictionary(IWrappedDictionary dictionary, JsonReader reader) + { + Type dictionaryType = dictionary.UnderlyingDictionary.GetType(); + Type dictionaryKeyType = ReflectionUtils.GetDictionaryKeyType(dictionaryType); + Type dictionaryValueType = ReflectionUtils.GetDictionaryValueType(dictionaryType); + + do + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + if (reader.Value is string && string.Equals(reader.Value.ToString(), JsonTypeReflector.IdPropertyName, StringComparison.Ordinal)) + { + reader.Read(); + _serializer.ReferenceResolver.AddObjectReference(reader.Value.ToString(), dictionary.UnderlyingDictionary); + } + else + { + object keyValue = EnsureType(reader.Value, dictionaryKeyType); + reader.Read(); + + dictionary.Add(keyValue, CreateValue(reader, dictionaryValueType, null, null)); + } + break; + case JsonToken.EndObject: + return dictionary; + default: + throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); + } + } while (reader.Read()); + + throw new JsonSerializationException("Unexpected end when deserializing object."); + } + + private object CreateAndPopulateList(JsonReader reader, Type objectType, string reference) + { + if (IsTypeGenericCollectionInterface(objectType)) + { + Type itemType = ReflectionUtils.GetCollectionItemType(objectType); + objectType = ReflectionUtils.MakeGenericType(typeof(List<>), itemType); + } + + return CollectionUtils.CreateAndPopulateList(objectType, (l, isTemporaryListReference) => + { + if (reference != null && isTemporaryListReference) + throw new JsonSerializationException("Cannot preserve reference to array or readonly list: {0}".FormatWith(CultureInfo.InvariantCulture, objectType)); + + PopulateList(l, ReflectionUtils.GetCollectionItemType(objectType), reader, reference); + }); + } + + private IList PopulateList(IList list, Type listItemType, JsonReader reader, string reference) + { + if (reference != null) + _serializer.ReferenceResolver.AddObjectReference(reference, list); + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.EndArray: + return list; + case JsonToken.Comment: + break; + default: + object value = CreateValue(reader, listItemType, null, null); + + list.Add(value); + break; + } + } + + throw new JsonSerializationException("Unexpected end when deserializing array."); + } + + private bool IsTypeGenericDictionaryInterface(Type type) + { + if (!type.IsGenericType) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + + return (genericDefinition == typeof(IDictionary<,>)); + } + + private bool IsTypeGenericCollectionInterface(Type type) + { + if (!type.IsGenericType) + return false; + + Type genericDefinition = type.GetGenericTypeDefinition(); + + return (genericDefinition == typeof(IList<>) + || genericDefinition == typeof(ICollection<>) + || genericDefinition == typeof(IEnumerable<>)); + } + + private object CreateAndPopulateObject(JsonReader reader, Type objectType) + { + object newObject; + + if (objectType.IsInterface || objectType.IsAbstract) + throw new JsonSerializationException("Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantated.".FormatWith(CultureInfo.InvariantCulture, objectType)); + + if (ReflectionUtils.HasDefaultConstructor(objectType)) + { + newObject = Activator.CreateInstance(objectType); + + PopulateObject(newObject, reader, objectType); + return newObject; + } + + return CreateObjectFromNonDefaultConstructor(objectType, reader); + } + + private object CreateObjectFromNonDefaultConstructor(Type objectType, JsonReader reader) + { + // object should have a single constructor + ConstructorInfo c = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).SingleOrDefault(); + + if (c == null) + throw new JsonSerializationException("Could not find a public constructor for type {0}.".FormatWith(CultureInfo.InvariantCulture, objectType)); + + // create a dictionary to put retrieved values into + JsonMemberMappingCollection memberMappings = _serializer.GetMemberMappings(objectType); + IDictionary mappingValues = memberMappings.ToDictionary(kv => kv, kv => (object)null); + + bool exit = false; + do + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + string memberName = reader.Value.ToString(); + if (!reader.Read()) + throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); + + JsonMemberMapping memberMapping; + // attempt exact case match first + // then try match ignoring case + if (memberMappings.TryGetClosestMatchMapping(memberName, out memberMapping)) + { + if (!memberMapping.Ignored) + { + Type memberType = ReflectionUtils.GetMemberUnderlyingType(memberMapping.Member); + mappingValues[memberMapping] = CreateValue(reader, memberType, null, memberMapping.MemberConverter); + } + } + else + { + if (_serializer.MissingMemberHandling == MissingMemberHandling.Error) + throw new JsonSerializationException("Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, objectType.Name)); + + reader.Skip(); + } + break; + case JsonToken.EndObject: + exit = true; + break; + default: + throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); + } + } while (!exit && reader.Read()); + + IDictionary constructorParameters = c.GetParameters().ToDictionary(p => p, p => (object)null); + IDictionary remainingMappingValues = new Dictionary(); + + foreach (KeyValuePair mappingValue in mappingValues) + { + ParameterInfo matchingConstructorParameter = constructorParameters.ForgivingCaseSensitiveFind(kv => kv.Key.Name, mappingValue.Key.MappingName).Key; + if (matchingConstructorParameter != null) + constructorParameters[matchingConstructorParameter] = mappingValue.Value; + else + remainingMappingValues.Add(mappingValue); + } + + object createdObject = ReflectionUtils.CreateInstance(objectType, constructorParameters.Values.ToArray()); + + // go through unused values and set the newly created object's properties + foreach (KeyValuePair remainingMappingValue in remainingMappingValues) + { + if (ShouldSetMappingValue(remainingMappingValue.Key, remainingMappingValue.Value)) + ReflectionUtils.SetMemberValue(remainingMappingValue.Key.Member, createdObject, remainingMappingValue.Value); + } + + return createdObject; + } + + private object PopulateObject(object newObject, JsonReader reader, Type objectType) + { + JsonMemberMappingCollection memberMappings = _serializer.GetMemberMappings(objectType); + Dictionary requiredMappings = + memberMappings.Where(m => m.Required).ToDictionary(m => m.MappingName, m => false); + + do + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + string memberName = reader.Value.ToString(); + + if (!reader.Read()) + throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); + + if (string.Equals(memberName, JsonTypeReflector.IdPropertyName, StringComparison.Ordinal)) + { + _serializer.ReferenceResolver.AddObjectReference(reader.Value.ToString(), newObject); + } + else + { + if (reader.TokenType != JsonToken.Null) + SetRequiredMapping(memberName, requiredMappings); + + SetObjectMember(reader, newObject, objectType, memberName); + } + break; + case JsonToken.EndObject: + foreach (KeyValuePair requiredMapping in requiredMappings) + { + if (!requiredMapping.Value) + throw new JsonSerializationException("Required property '{0}' not found in JSON.".FormatWith(CultureInfo.InvariantCulture, requiredMapping.Key)); + } + return newObject; + default: + throw new JsonSerializationException("Unexpected token when deserializing object: " + reader.TokenType); + } + } while (reader.Read()); + + throw new JsonSerializationException("Unexpected end when deserializing object."); + } + + private void SetRequiredMapping(string memberName, Dictionary requiredMappings) + { + // first attempt to find exact case match + // then attempt case insensitive match + if (requiredMappings.ContainsKey(memberName)) + { + requiredMappings[memberName] = true; + } + else + { + foreach (KeyValuePair requiredMapping in requiredMappings) + { + if (string.Compare(requiredMapping.Key, requiredMapping.Key, StringComparison.OrdinalIgnoreCase) == 0) + { + requiredMappings[requiredMapping.Key] = true; + break; + } + } + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerWriter.cs new file mode 100644 index 00000000..5ece158f --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerWriter.cs @@ -0,0 +1,375 @@ +#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; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Serialization +{ + internal class JsonSerializerWriter + { + internal readonly JsonSerializer _serializer; + private JsonSerializerProxy _internalSerializer; + private List _serializeStack; + + private List SerializeStack + { + get + { + if (_serializeStack == null) + _serializeStack = new List(); + + return _serializeStack; + } + } + + public JsonSerializerWriter(JsonSerializer serializer) + { + ValidationUtils.ArgumentNotNull(serializer, "serializer"); + + _serializer = serializer; + } + + public void Serialize(JsonWriter jsonWriter, object value) + { + if (jsonWriter == null) + throw new ArgumentNullException("jsonWriter"); + + if (value is JToken) + ((JToken)value).WriteTo(jsonWriter, (_serializer.Converters != null) ? _serializer.Converters.ToArray() : null); + else + SerializeValue(jsonWriter, value, null); + } + + private JsonSerializerProxy GetInternalSerializer() + { + if (_internalSerializer == null) + _internalSerializer = new JsonSerializerProxy(this); + + return _internalSerializer; + } + + private void SerializeValue(JsonWriter writer, object value, JsonConverter memberConverter) + { + JsonConverter converter = memberConverter; + + if (value == null) + { + writer.WriteNull(); + } + else if (converter != null + || _serializer.HasClassConverter(value.GetType(), out converter) + || _serializer.HasMatchingConverter(value.GetType(), out converter)) + { + SerializeConvertable(writer, converter, value); + } + else if (JsonConvert.IsJsonPrimitive(value)) + { + writer.WriteValue(value); + } + else if (value is IList) + { + SerializeList(writer, (IList)value); + } + else if (value is IDictionary) + { + SerializeDictionary(writer, (IDictionary)value); + } + else if (value is IEnumerable) + { + SerializeEnumerable(writer, (IEnumerable)value); + } + else if (value is JsonRaw) + { + writer.WriteRawValue(((JsonRaw)value).Content); + } + else + { + SerializeObject(writer, value); + } + } + + private bool ShouldWriteReference(object value) + { + if (value == null) + return false; + if (JsonConvert.IsJsonPrimitive(value)) + return false; + + Type t = value.GetType(); + + bool? isReference = IsReference(t); + + if (isReference == null) + { + if (value is IList) + isReference = HasFlag(_serializer.PreserveReferencesHandling, PreserveReferencesHandling.Arrays); + else if (value is IDictionary) + isReference = HasFlag(_serializer.PreserveReferencesHandling, PreserveReferencesHandling.Objects); + else if (value is IEnumerable) + isReference = HasFlag(_serializer.PreserveReferencesHandling, PreserveReferencesHandling.Arrays); + else + isReference = HasFlag(_serializer.PreserveReferencesHandling, PreserveReferencesHandling.Objects); + } + + if (!isReference.Value) + return false; + + return _serializer.ReferenceResolver.HasObjectReference(value); + } + + private void WriteMemberInfoProperty(JsonWriter writer, object value, JsonMemberMapping memberMapping) + { + MemberInfo member = memberMapping.Member; + string propertyName = memberMapping.MappingName; + JsonConverter memberConverter = memberMapping.MemberConverter; + object defaultValue = memberMapping.DefaultValue; + + if (!ReflectionUtils.IsIndexedProperty(member)) + { + object memberValue = ReflectionUtils.GetMemberValue(member, value); + + if (memberMapping.NullValueHandling.GetValueOrDefault(_serializer.NullValueHandling) == NullValueHandling.Ignore && memberValue == null) + return; + + if (memberMapping.DefaultValueHandling.GetValueOrDefault(_serializer.DefaultValueHandling) == DefaultValueHandling.Ignore && Equals(memberValue, defaultValue)) + return; + + if (ShouldWriteReference(memberValue)) + { + writer.WritePropertyName(propertyName ?? member.Name); + WriteReference(writer, memberValue); + return; + } + + if (!CheckForCircularReference(memberValue, memberMapping.ReferenceLoopHandling)) + return; + + writer.WritePropertyName(propertyName ?? member.Name); + SerializeValue(writer, memberValue, memberConverter); + } + } + + private bool CheckForCircularReference(object value, ReferenceLoopHandling? referenceLoopHandling) + { + if (SerializeStack.IndexOf(value) != -1) + { + switch (referenceLoopHandling.GetValueOrDefault(_serializer.ReferenceLoopHandling)) + { + case ReferenceLoopHandling.Error: + throw new JsonSerializationException("Self referencing loop"); + case ReferenceLoopHandling.Ignore: + return false; + case ReferenceLoopHandling.Serialize: + return true; + default: + throw new InvalidOperationException("Unexpected ReferenceLoopHandling value: '{0}'".FormatWith(CultureInfo.InvariantCulture, _serializer.ReferenceLoopHandling)); + } + } + + return true; + } + + private void WriteReference(JsonWriter writer, object value) + { + writer.WriteStartObject(); + writer.WritePropertyName(JsonTypeReflector.RefPropertyName); + writer.WriteValue(_serializer.ReferenceResolver.ResolveReference(value)); + writer.WriteEndObject(); + } + + private void SerializeObject(JsonWriter writer, object value) + { + Type objectType = value.GetType(); + +#if !SILVERLIGHT && !PocketPC + TypeConverter converter = TypeDescriptor.GetConverter(objectType); + + // use the objectType's TypeConverter if it has one and can convert to a string + if (converter != null && !(converter is ComponentConverter) && (converter.GetType() != typeof (TypeConverter) || value is Type)) + { + if (converter.CanConvertTo(typeof (string))) + { + writer.WriteValue(converter.ConvertToInvariantString(value)); + return; + } + } +#else + if (value is Guid || value is Type) + { + writer.WriteValue(value.ToString()); + return; + } +#endif + + + + SerializeStack.Add(value); + writer.WriteStartObject(); + + bool isReference = IsReference(objectType) ?? HasFlag(_serializer.PreserveReferencesHandling, PreserveReferencesHandling.Objects); + if (isReference) + { + writer.WritePropertyName(JsonTypeReflector.IdPropertyName); + writer.WriteValue(_serializer.ReferenceResolver.ResolveReference(value)); + } + + JsonMemberMappingCollection memberMappings = _serializer.GetMemberMappings(objectType); + + foreach (JsonMemberMapping memberMapping in memberMappings) + { + if (!memberMapping.Ignored && memberMapping.Readable) + WriteMemberInfoProperty(writer, value, memberMapping); + } + + writer.WriteEndObject(); + SerializeStack.RemoveAt(SerializeStack.Count - 1); + } + + private bool HasFlag(PreserveReferencesHandling value, PreserveReferencesHandling flag) + { + return ((value & flag) == flag); + } + + private bool? IsReference(Type t) + { + JsonContainerAttribute containerAttribute = JsonTypeReflector.GetJsonContainerAttribute(t); + if (containerAttribute == null) + return null; + + return containerAttribute._isReference; + } + + private void SerializeConvertable(JsonWriter writer, JsonConverter converter, object value) + { + if (ShouldWriteReference(value)) + { + WriteReference(writer, value); + } + else + { + if (!CheckForCircularReference(value, null)) + return; + + SerializeStack.Add(value); + + converter.WriteJson(writer, value, GetInternalSerializer()); + + SerializeStack.RemoveAt(SerializeStack.Count - 1); + } + } + + private void SerializeEnumerable(JsonWriter writer, IEnumerable values) + { + SerializeList(writer, values.Cast().ToList()); + } + + private void SerializeList(JsonWriter writer, IList values) + { + SerializeStack.Add(values); + + bool isReference = IsReference(values.GetType()) ?? HasFlag(_serializer.PreserveReferencesHandling, PreserveReferencesHandling.Arrays); + if (isReference) + { + writer.WriteStartObject(); + writer.WritePropertyName(JsonTypeReflector.IdPropertyName); + writer.WriteValue(_serializer.ReferenceResolver.ResolveReference(values)); + + writer.WritePropertyName("value"); + } + + writer.WriteStartArray(); + + for (int i = 0; i < values.Count; i++) + { + object value = values[i]; + + if (ShouldWriteReference(value)) + { + WriteReference(writer, value); + } + else + { + if (!CheckForCircularReference(value, null)) + continue; + + SerializeValue(writer, value, null); + } + } + + writer.WriteEndArray(); + + if (isReference) + { + writer.WriteEndObject(); + } + + SerializeStack.RemoveAt(SerializeStack.Count - 1); + } + + private void SerializeDictionary(JsonWriter writer, IDictionary values) + { + SerializeStack.Add(values); + writer.WriteStartObject(); + + bool isReference = IsReference(values.GetType()) ?? HasFlag(_serializer.PreserveReferencesHandling, PreserveReferencesHandling.Objects); + if (isReference) + { + writer.WritePropertyName(JsonTypeReflector.IdPropertyName); + writer.WriteValue(_serializer.ReferenceResolver.ResolveReference(values)); + } + + foreach (DictionaryEntry entry in values) + { + string propertyName = entry.Key.ToString(); + object value = entry.Value; + + if (ShouldWriteReference(value)) + { + writer.WritePropertyName(propertyName); + WriteReference(writer, value); + } + else + { + if (!CheckForCircularReference(value, null)) + continue; + + writer.WritePropertyName(propertyName); + SerializeValue(writer, value, null); + } + } + + writer.WriteEndObject(); + SerializeStack.RemoveAt(SerializeStack.Count - 1); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index 7f334f94..50598825 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -38,6 +38,9 @@ namespace Newtonsoft.Json.Serialization { internal static class JsonTypeReflector { + public const string IdPropertyName = "$id"; + public const string RefPropertyName = "$ref"; + private static readonly Dictionary ConverterTypeCache = new Dictionary(); private static readonly Dictionary AssociatedMetadataTypesCache = new Dictionary(); diff --git a/Src/Newtonsoft.Json/Utilities/BidirectionalDictionary.cs b/Src/Newtonsoft.Json/Utilities/BidirectionalDictionary.cs new file mode 100644 index 00000000..f651f949 --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/BidirectionalDictionary.cs @@ -0,0 +1,58 @@ +#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.Linq; +using System.Text; + +namespace Newtonsoft.Json.Utilities +{ + internal class BidirectionalDictionary + { + private readonly IDictionary _firstToSecond = new Dictionary(); + private readonly IDictionary _secondToFirst = new Dictionary(); + + public void Add(TFirst first, TSecond second) + { + if (_firstToSecond.ContainsKey(first) || _secondToFirst.ContainsKey(second)) + { + throw new ArgumentException("Duplicate first or second"); + } + _firstToSecond.Add(first, second); + _secondToFirst.Add(second, first); + } + + public bool TryGetByFirst(TFirst first, out TSecond second) + { + return _firstToSecond.TryGetValue(first, out second); + } + + public bool TryGetBySecond(TSecond second, out TFirst first) + { + return _secondToFirst.TryGetValue(second, out first); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs index cace4bbc..15feeb83 100644 --- a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs @@ -484,7 +484,7 @@ namespace Newtonsoft.Json.Utilities } } - public static IList CreateAndPopulateList(Type listType, Action populateList) + public static IList CreateAndPopulateList(Type listType, Action populateList) { ValidationUtils.ArgumentNotNull(listType, "listType"); ValidationUtils.ArgumentNotNull(populateList, "populateList"); @@ -549,7 +549,7 @@ namespace Newtonsoft.Json.Utilities if (list == null) throw new Exception("Cannot create and populate list type {0}.".FormatWith(CultureInfo.InvariantCulture, listType)); - populateList(list); + populateList(list, isReadOnlyOrFixedSize); // create readonly and fixed sized collections using the temporary list if (isReadOnlyOrFixedSize) diff --git a/Src/Newtonsoft.Json/StringBuffer.cs b/Src/Newtonsoft.Json/Utilities/StringBuffer.cs similarity index 95% rename from Src/Newtonsoft.Json/StringBuffer.cs rename to Src/Newtonsoft.Json/Utilities/StringBuffer.cs index 574af4a1..b458f397 100644 --- a/Src/Newtonsoft.Json/StringBuffer.cs +++ b/Src/Newtonsoft.Json/Utilities/StringBuffer.cs @@ -25,7 +25,7 @@ using System; -namespace Newtonsoft.Json +namespace Newtonsoft.Json.Utilities { /// /// Builds a string. Unlike StringBuilder this class lets you reuse it's internal buffer.