diff --git a/Src/Newtonsoft.Json.Tests/EntityDataModel.Designer.cs b/Src/Newtonsoft.Json.Tests/EntityDataModel.Designer.cs new file mode 100644 index 00000000..954fa14a --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/EntityDataModel.Designer.cs @@ -0,0 +1,242 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.3053 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +[assembly: global::System.Data.Objects.DataClasses.EdmSchemaAttribute()] +[assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("EntityDataModel", "PurchasePurchaseLine", "Purchase", global::System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(Newtonsoft.Json.Tests.Purchase), "PurchaseLine", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(Newtonsoft.Json.Tests.PurchaseLine))] + +// Original file name: +// Generation date: 7/09/2008 7:58:08 p.m. +namespace Newtonsoft.Json.Tests +{ + + /// + /// There are no comments for EntityDataModelContainer in the schema. + /// + public partial class EntityDataModelContainer : global::System.Data.Objects.ObjectContext + { + /// + /// Initializes a new EntityDataModelContainer object using the connection string found in the 'EntityDataModelContainer' section of the application configuration file. + /// + public EntityDataModelContainer() : + base("name=EntityDataModelContainer", "EntityDataModelContainer") + { + this.OnContextCreated(); + } + /// + /// Initialize a new EntityDataModelContainer object. + /// + public EntityDataModelContainer(string connectionString) : + base(connectionString, "EntityDataModelContainer") + { + this.OnContextCreated(); + } + /// + /// Initialize a new EntityDataModelContainer object. + /// + public EntityDataModelContainer(global::System.Data.EntityClient.EntityConnection connection) : + base(connection, "EntityDataModelContainer") + { + this.OnContextCreated(); + } + partial void OnContextCreated(); + /// + /// There are no comments for PurchaseSet in the schema. + /// + public global::System.Data.Objects.ObjectQuery PurchaseSet + { + get + { + if ((this._PurchaseSet == null)) + { + this._PurchaseSet = base.CreateQuery("[PurchaseSet]"); + } + return this._PurchaseSet; + } + } + private global::System.Data.Objects.ObjectQuery _PurchaseSet; + /// + /// There are no comments for PurchaseLineSet in the schema. + /// + public global::System.Data.Objects.ObjectQuery PurchaseLineSet + { + get + { + if ((this._PurchaseLineSet == null)) + { + this._PurchaseLineSet = base.CreateQuery("[PurchaseLineSet]"); + } + return this._PurchaseLineSet; + } + } + private global::System.Data.Objects.ObjectQuery _PurchaseLineSet; + /// + /// There are no comments for PurchaseSet in the schema. + /// + public void AddToPurchaseSet(Purchase purchase) + { + base.AddObject("PurchaseSet", purchase); + } + /// + /// There are no comments for PurchaseLineSet in the schema. + /// + public void AddToPurchaseLineSet(PurchaseLine purchaseLine) + { + base.AddObject("PurchaseLineSet", purchaseLine); + } + } + /// + /// There are no comments for EntityDataModel.Purchase in the schema. + /// + /// + /// Id + /// + [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="EntityDataModel", Name="Purchase")] + [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] + [global::System.Serializable()] + public partial class Purchase : global::System.Data.Objects.DataClasses.EntityObject + { + /// + /// Create a new Purchase object. + /// + /// Initial value of Id. + public static Purchase CreatePurchase(int id) + { + Purchase purchase = new Purchase(); + purchase.Id = id; + return purchase; + } + /// + /// There are no comments for Property Id in the schema. + /// + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public int Id + { + get + { + return this._Id; + } + set + { + this.OnIdChanging(value); + this.ReportPropertyChanging("Id"); + this._Id = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("Id"); + this.OnIdChanged(); + } + } + private int _Id; + partial void OnIdChanging(int value); + partial void OnIdChanged(); + /// + /// There are no comments for PurchaseLine in the schema. + /// + [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("EntityDataModel", "PurchasePurchaseLine", "PurchaseLine")] + [global::System.Xml.Serialization.XmlIgnoreAttribute()] + [global::System.Xml.Serialization.SoapIgnoreAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public global::System.Data.Objects.DataClasses.EntityCollection PurchaseLine + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedCollection("EntityDataModel.PurchasePurchaseLine", "PurchaseLine"); + } + set + { + if ((value != null)) + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedCollection("EntityDataModel.PurchasePurchaseLine", "PurchaseLine", value); + } + } + } + } + /// + /// There are no comments for EntityDataModel.PurchaseLine in the schema. + /// + /// + /// Id + /// + [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="EntityDataModel", Name="PurchaseLine")] + [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] + [global::System.Serializable()] + public partial class PurchaseLine : global::System.Data.Objects.DataClasses.EntityObject + { + /// + /// Create a new PurchaseLine object. + /// + /// Initial value of Id. + public static PurchaseLine CreatePurchaseLine(int id) + { + PurchaseLine purchaseLine = new PurchaseLine(); + purchaseLine.Id = id; + return purchaseLine; + } + /// + /// There are no comments for Property Id in the schema. + /// + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public int Id + { + get + { + return this._Id; + } + set + { + this.OnIdChanging(value); + this.ReportPropertyChanging("Id"); + this._Id = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("Id"); + this.OnIdChanged(); + } + } + private int _Id; + partial void OnIdChanging(int value); + partial void OnIdChanged(); + /// + /// There are no comments for Purchase in the schema. + /// + [global::System.Data.Objects.DataClasses.EdmRelationshipNavigationPropertyAttribute("EntityDataModel", "PurchasePurchaseLine", "Purchase")] + [global::System.Xml.Serialization.XmlIgnoreAttribute()] + [global::System.Xml.Serialization.SoapIgnoreAttribute()] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public Purchase Purchase + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference("EntityDataModel.PurchasePurchaseLine", "Purchase").Value; + } + set + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference("EntityDataModel.PurchasePurchaseLine", "Purchase").Value = value; + } + } + /// + /// There are no comments for Purchase in the schema. + /// + [global::System.ComponentModel.BrowsableAttribute(false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + public global::System.Data.Objects.DataClasses.EntityReference PurchaseReference + { + get + { + return ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference("EntityDataModel.PurchasePurchaseLine", "Purchase"); + } + set + { + if ((value != null)) + { + ((global::System.Data.Objects.DataClasses.IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedReference("EntityDataModel.PurchasePurchaseLine", "Purchase", value); + } + } + } + } +} diff --git a/Src/Newtonsoft.Json.Tests/EntityDataModel.edmx b/Src/Newtonsoft.Json.Tests/EntityDataModel.edmx new file mode 100644 index 00000000..aec3aa44 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/EntityDataModel.edmx @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JavaScriptConvertTest.cs b/Src/Newtonsoft.Json.Tests/JavaScriptConvertTest.cs index 44074958..0f65a8f0 100644 --- a/Src/Newtonsoft.Json.Tests/JavaScriptConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JavaScriptConvertTest.cs @@ -28,11 +28,77 @@ using System.Collections.Generic; using System.Text; using Newtonsoft.Json.Utilities; using NUnit.Framework; +using System.IO; +using System.Linq; namespace Newtonsoft.Json.Tests { public class JavaScriptConvertTest : TestFixtureBase { +#if Entities + [Test] + public void EntitiesTest() + { + Purchase purchase = new Purchase() { Id = 1 }; + purchase.PurchaseLine.Add(new PurchaseLine() { Id = 1, Purchase = purchase }); + purchase.PurchaseLine.Add(new PurchaseLine() { Id = 2, Purchase = purchase }); + + StringWriter sw = new StringWriter(); + JsonSerializer serializer = new JsonSerializer(); + serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + + using (JsonWriter jw = new JsonTextWriter(sw)) + { + jw.Formatting = Formatting.Indented; + + serializer.Serialize(jw, purchase); + } + + string json = sw.ToString(); + + Assert.AreEqual(@"{ + ""Id"": 1, + ""PurchaseLine"": [ + { + ""Id"": 1, + ""PurchaseReference"": { + ""EntityKey"": null, + ""RelationshipName"": ""EntityDataModel.PurchasePurchaseLine"", + ""SourceRoleName"": ""PurchaseLine"", + ""TargetRoleName"": ""Purchase"", + ""RelationshipSet"": null, + ""IsLoaded"": false + }, + ""EntityState"": 1, + ""EntityKey"": null + }, + { + ""Id"": 2, + ""PurchaseReference"": { + ""EntityKey"": null, + ""RelationshipName"": ""EntityDataModel.PurchasePurchaseLine"", + ""SourceRoleName"": ""PurchaseLine"", + ""TargetRoleName"": ""Purchase"", + ""RelationshipSet"": null, + ""IsLoaded"": false + }, + ""EntityState"": 1, + ""EntityKey"": null + } + ], + ""EntityState"": 1, + ""EntityKey"": null +}", json); + + Purchase newPurchase = JavaScriptConvert.DeserializeObject(json); + Assert.AreEqual(1, newPurchase.Id); + + Assert.AreEqual(2, newPurchase.PurchaseLine.Count); + Assert.AreEqual(1, newPurchase.PurchaseLine.ElementAt(0).Id); + Assert.AreEqual(2, newPurchase.PurchaseLine.ElementAt(1).Id); + } +#endif + [Test] public void EscapeJavaScriptString() { diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 234cadac..e4c5d2cf 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -51,9 +51,13 @@ 3.5 + + 3.5 + 3.0 + 3.0 @@ -84,6 +88,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs new file mode 100644 index 00000000..382ce5ea --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Data; +using System.Diagnostics; + +using Newtonsoft.Json; +using System.IO; +using System.Web.Script.Serialization; +using NUnit.Framework; + +namespace Newtonsoft.Json.Tests +{ + /// + /// Summary description for JsonSerializerTest + /// + public class PerformanceTests : TestFixtureBase + { + private DateTime BaseDate = DateTime.Parse("01/01/2000"); + + //JSONSerializer ser = null; + + public string SerializeJsonNet(object value) + { + Type type = value.GetType(); + + Newtonsoft.Json.JsonSerializer json = new Newtonsoft.Json.JsonSerializer(); + + json.NullValueHandling = NullValueHandling.Ignore; + + json.ObjectCreationHandling = Newtonsoft.Json.ObjectCreationHandling.Replace; + json.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Ignore; + json.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + + + StringWriter sw = new StringWriter(); + Newtonsoft.Json.JsonTextWriter writer = new JsonTextWriter(sw); + + if (false) + writer.Formatting = Formatting.Indented; + else + writer.Formatting = Formatting.None; + + writer.QuoteChar = '"'; + json.Serialize(writer, value); + + string output = sw.ToString(); + writer.Close(); + + return output; + } + + public string SerializeWebExtensions(object value) + { + JavaScriptSerializer ser = new JavaScriptSerializer(); + + List converters = new List(); + + return ser.Serialize(value); + } + + + [Test] + public void PerformanceTest2() + { + + TestClass test = new TestClass(); + + test.Address1.Address = "fff Street"; + test.Address1.Entered = DateTime.Now.AddDays(20); + + test.BigNumber = 34123123123.121M; + test.Now = DateTime.Now.AddHours(1); + test.strings = new List() { "Rick", "Markus egger ][, (2nd)", "Kevin McNeish" }; + + cAddress address = new cAddress(); + address.Entered = DateTime.Now.AddDays(-1); + address.Address = "array address"; + + test.Addresses.Add(address); + + address = new cAddress(); + address.Entered = DateTime.Now.AddDays(-2); + address.Address = "array 2 address"; + test.Addresses.Add(address); + + Stopwatch timed = new Stopwatch(); + timed.Start(); + + string json = null; + for (int x = 0; x < 10000; x++) + { + json = this.SerializeJsonNet(test); + //json = this.SerializeWebExtensions(test); + } + timed.Stop(); + + Console.WriteLine("{0}", timed.ElapsedMilliseconds + " ms"); + Console.WriteLine("{0}", json); + } + } + + + public class TestClass + { + + public string Name + { + get { return _Name; } + set { _Name = value; } + } + private string _Name = "Rick"; + + + public DateTime Now + { + get { return _Now; } + set { _Now = value; } + } + private DateTime _Now = DateTime.Now; + + + public decimal BigNumber + { + get { return _BigNumber; } + set { _BigNumber = value; } + } + private decimal _BigNumber = 1212121.22M; + + + public cAddress Address1 + { + get { return _Address1; } + set { _Address1 = value; } + } + private cAddress _Address1 = new cAddress(); + + + + + public List Addresses + { + get { return _Addresses; } + set { _Addresses = value; } + } + private List _Addresses = new List(); + + + public List strings = new List() { "Rick", "Markus", "Kevin" }; + + + public Dictionary dictionary = new Dictionary { { "Val asd1", 1 }, { "Val2", 3 }, { "Val3", 4 } }; + } + + public class cAddress + { + + public string Address + { + get { return _Address; } + set { _Address = value; } + } + private string _Address = "32 Kaiea"; + + + public string Phone + { + get { return _Phone; } + set { _Phone = value; } + } + private string _Phone = "(503) 814-6335"; + + + public DateTime Entered + { + get { return _Entered; } + set { _Entered = value; } + } + private DateTime _Entered = DateTime.Parse("01/01/2007", CultureInfo.CurrentCulture.DateTimeFormat); + + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonConverterAttribute.cs b/Src/Newtonsoft.Json/JsonConverterAttribute.cs index 9ee2dcfa..cd92ed0a 100644 --- a/Src/Newtonsoft.Json/JsonConverterAttribute.cs +++ b/Src/Newtonsoft.Json/JsonConverterAttribute.cs @@ -36,15 +36,15 @@ namespace Newtonsoft.Json _converterType = converterType; } - internal JsonConverter CreateJsonConverterInstance() + internal static JsonConverter CreateJsonConverterInstance(Type converterType) { try { - return (JsonConverter)Activator.CreateInstance(_converterType); + return (JsonConverter)Activator.CreateInstance(converterType); } catch (Exception ex) { - throw new Exception("Error creating {0}".FormatWith(CultureInfo.InvariantCulture, _converterType), ex); + throw new Exception("Error creating {0}".FormatWith(CultureInfo.InvariantCulture, converterType), ex); } } } diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index 2cfabe3f..2e70532c 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -44,13 +44,15 @@ namespace Newtonsoft.Json /// public class JsonSerializer { + private static readonly Dictionary ConverterTypeCache = new Dictionary(); + private static Dictionary TypeMemberMappings = new Dictionary(); + private ReferenceLoopHandling _referenceLoopHandling; private MissingMemberHandling _missingMemberHandling; private ObjectCreationHandling _objectCreationHandling; private NullValueHandling _nullValueHandling; private int _level; private JsonConverterCollection _converters; - private Dictionary _typeMemberMappings; /// /// Get or set how reference loops (e.g. a class referencing itself) is handled. @@ -255,7 +257,7 @@ namespace Newtonsoft.Json if (existingValue == null) value = CreateAndPopulateList(reader, objectType); else - value = PopulateList(CollectionUtils.CreateListWrapper(existingValue), ReflectionUtils.GetListItemType(objectType), reader); + value = PopulateList(CollectionUtils.CreateCollectionWrapper(existingValue), ReflectionUtils.GetCollectionItemType(objectType), reader); } else { @@ -317,16 +319,13 @@ namespace Newtonsoft.Json private MemberMappingCollection GetMemberMappings(Type objectType) { - if (_typeMemberMappings == null) - _typeMemberMappings = new Dictionary(); - MemberMappingCollection memberMappings; - if (_typeMemberMappings.TryGetValue(objectType, out memberMappings)) + if (TypeMemberMappings.TryGetValue(objectType, out memberMappings)) return memberMappings; memberMappings = CreateMemberMappings(objectType); - _typeMemberMappings[objectType] = memberMappings; + TypeMemberMappings[objectType] = memberMappings; return memberMappings; } @@ -388,11 +387,25 @@ namespace Newtonsoft.Json private JsonConverter GetConverter(ICustomAttributeProvider attributeProvider, Type targetConvertedType) { - JsonConverterAttribute converterAttribute = ReflectionUtils.GetAttribute(attributeProvider, true); + Type converterType; - if (converterAttribute != null) + if (!ConverterTypeCache.ContainsKey(attributeProvider)) { - JsonConverter memberConverter = converterAttribute.CreateJsonConverterInstance(); + JsonConverterAttribute converterAttribute = ReflectionUtils.GetAttribute(attributeProvider, true); + converterType = (converterAttribute != null) + ? converterAttribute.ConverterType + : null; + + ConverterTypeCache[attributeProvider] = converterType; + } + else + { + converterType = ConverterTypeCache[attributeProvider]; + } + + if (converterType != null) + { + JsonConverter memberConverter = JsonConverterAttribute.CreateJsonConverterInstance(converterType); if (!memberConverter.CanConvert(targetConvertedType)) throw new JsonSerializationException("JsonConverter {0} on {1} is not compatible with member type {2}.".FormatWith(CultureInfo.InvariantCulture, memberConverter.GetType().Name, attributeProvider, targetConvertedType.Name)); @@ -490,7 +503,7 @@ namespace Newtonsoft.Json private object CreateAndPopulateList(JsonReader reader, Type objectType) { - return CollectionUtils.CreateAndPopulateList(objectType, l => PopulateList(l, ReflectionUtils.GetListItemType(objectType), reader)); + return CollectionUtils.CreateAndPopulateList(objectType, l => PopulateList(l, ReflectionUtils.GetCollectionItemType(objectType), reader)); } private IList PopulateList(IList list, Type listItemType, JsonReader reader) diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 1b50a976..2b9553c4 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -116,6 +116,7 @@ + diff --git a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs index 56736cca..ac07f550 100644 --- a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs @@ -366,6 +366,33 @@ namespace Newtonsoft.Json.Utilities return false; } + public static IWrappedCollection CreateCollectionWrapper(object list) + { + ValidationUtils.ArgumentNotNull(list, "list"); + + Type collectionDefinition; + if (ReflectionUtils.IsSubClass(list.GetType(), typeof(ICollection<>), out collectionDefinition)) + { + Type collectionItemType = ReflectionUtils.GetCollectionItemType(collectionDefinition); + + // Activator.CreateInstance throws AmbiguousMatchException. Manually invoke constructor + Func, object> instanceCreator = (t, a) => + { + ConstructorInfo c = t.GetConstructor(new[] { collectionDefinition }); + return c.Invoke(new[] { list }); + }; + + return (IWrappedCollection)ReflectionUtils.CreateGeneric(typeof(CollectionWrapper<>), new[] { collectionItemType }, instanceCreator, list); + } + else if (list is IList) + { + return new CollectionWrapper((IList)list); + } + else + { + throw new Exception("Can not create ListWrapper for type {0}.".FormatWith(CultureInfo.InvariantCulture, list.GetType())); + } + } public static IWrappedList CreateListWrapper(object list) { ValidationUtils.ArgumentNotNull(list, "list"); @@ -373,7 +400,7 @@ namespace Newtonsoft.Json.Utilities Type listDefinition; if (ReflectionUtils.IsSubClass(list.GetType(), typeof(IList<>), out listDefinition)) { - Type listItemType = ReflectionUtils.GetListItemType(listDefinition); + Type collectionItemType = ReflectionUtils.GetCollectionItemType(listDefinition); // Activator.CreateInstance throws AmbiguousMatchException. Manually invoke constructor Func, object> instanceCreator = (t, a) => @@ -382,7 +409,7 @@ namespace Newtonsoft.Json.Utilities return c.Invoke(new[] { list }); }; - return (IWrappedList)ReflectionUtils.CreateGeneric(typeof(ListWrapper<>), new[] { listItemType }, instanceCreator, list); + return (IWrappedList)ReflectionUtils.CreateGeneric(typeof(ListWrapper<>), new[] { collectionItemType }, instanceCreator, list); } else if (list is IList) { @@ -478,7 +505,7 @@ namespace Newtonsoft.Json.Utilities } else if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(IList<>)) { - list = CollectionUtils.CreateGenericList(ReflectionUtils.GetListItemType(listType)); + list = CollectionUtils.CreateGenericList(ReflectionUtils.GetCollectionItemType(listType)); } else { @@ -494,7 +521,7 @@ namespace Newtonsoft.Json.Utilities if (isReadOnlyOrFixedSize) { if (listType.IsArray) - list = ToArray(((List)list).ToArray(), ReflectionUtils.GetListItemType(listType)); + list = ToArray(((List)list).ToArray(), ReflectionUtils.GetCollectionItemType(listType)); else if (ReflectionUtils.IsSubClass(listType, typeof(ReadOnlyCollection<>))) list = (IList)Activator.CreateInstance(listType, list); } diff --git a/Src/Newtonsoft.Json/Utilities/CollectionWrapper.cs b/Src/Newtonsoft.Json/Utilities/CollectionWrapper.cs new file mode 100644 index 00000000..0346aafd --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/CollectionWrapper.cs @@ -0,0 +1,268 @@ +#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.Threading; +using Newtonsoft.Json.Utilities; +using System.Linq; +using System.Globalization; + +namespace Newtonsoft.Json.Utilities +{ + internal interface IWrappedCollection : IList + { + object UnderlyingCollection { get; } + } + + internal class CollectionWrapper : ICollection, IWrappedCollection + { + private readonly IList _list; + private readonly ICollection _genericCollection; + private object _syncRoot; + + public CollectionWrapper(IList list) + { + ValidationUtils.ArgumentNotNull(list, "list"); + + _list = list; + } + + public CollectionWrapper(ICollection list) + { + ValidationUtils.ArgumentNotNull(list, "list"); + + _genericCollection = list; + } + + public void Add(T item) + { + if (_genericCollection != null) + _genericCollection.Add(item); + else + _list.Add(item); + } + + public void Clear() + { + if (_genericCollection != null) + _genericCollection.Clear(); + else + _list.Clear(); + } + + public bool Contains(T item) + { + if (_genericCollection != null) + return _genericCollection.Contains(item); + else + return _list.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (_genericCollection != null) + _genericCollection.CopyTo(array, arrayIndex); + else + _list.CopyTo(array, arrayIndex); + } + + public int Count + { + get + { + if (_genericCollection != null) + return _genericCollection.Count; + else + return _list.Count; + } + } + + public bool IsReadOnly + { + get + { + if (_genericCollection != null) + return _genericCollection.IsReadOnly; + else + return _list.IsReadOnly; + } + } + + public bool Remove(T item) + { + if (_genericCollection != null) + { + return _genericCollection.Remove(item); + } + else + { + bool contains = _list.Contains(item); + + if (contains) + _list.Remove(item); + + return contains; + } + } + + public IEnumerator GetEnumerator() + { + if (_genericCollection != null) + return _genericCollection.GetEnumerator(); + + return _list.Cast().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + if (_genericCollection != null) + return _genericCollection.GetEnumerator(); + else + return _list.GetEnumerator(); + } + + int IList.Add(object value) + { + VerifyValueType(value); + Add((T)value); + + return (Count - 1); + } + + bool IList.Contains(object value) + { + if (IsCompatibleObject(value)) + return Contains((T)value); + + return false; + } + + int IList.IndexOf(object value) + { + if (_genericCollection != null) + throw new Exception("Wrapped ICollection does not support IndexOf."); + + if (IsCompatibleObject(value)) + return _list.IndexOf((T)value); + + return -1; + } + + void IList.RemoveAt(int index) + { + if (_genericCollection != null) + throw new Exception("Wrapped ICollection does not support RemoveAt."); + + _list.RemoveAt(index); + } + + void IList.Insert(int index, object value) + { + if (_genericCollection != null) + throw new Exception("Wrapped ICollection does not support Insert."); + + VerifyValueType(value); + _list.Insert(index, (T)value); + } + + bool IList.IsFixedSize + { + get { return false; } + } + + void IList.Remove(object value) + { + if (IsCompatibleObject(value)) + Remove((T)value); + } + + object IList.this[int index] + { + get + { + if (_genericCollection != null) + throw new Exception("Wrapped ICollection does not support indexer."); + + return _list[index]; + } + set + { + if (_genericCollection != null) + throw new Exception("Wrapped ICollection does not support indexer."); + + VerifyValueType(value); + _list[index] = (T)value; + } + } + + void ICollection.CopyTo(Array array, int arrayIndex) + { + CopyTo((T[])array, arrayIndex); + } + + bool ICollection.IsSynchronized + { + get { return false; } + } + + object ICollection.SyncRoot + { + get + { + if (_syncRoot == null) + Interlocked.CompareExchange(ref _syncRoot, new object(), null); + + return _syncRoot; + } + } + + private static void VerifyValueType(object value) + { + if (!IsCompatibleObject(value)) + throw new ArgumentException("The value '{0}' is not of type '{1}' and cannot be used in this generic collection.".FormatWith(CultureInfo.InvariantCulture, value, typeof(T)), "value"); + } + + private static bool IsCompatibleObject(object value) + { + if (!(value is T) && (value != null || typeof(T).IsValueType)) + return false; + + return true; + } + + public object UnderlyingCollection + { + get + { + if (_genericCollection != null) + return _genericCollection; + else + return _list; + } + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index fe42106f..6919e32d 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -181,11 +181,11 @@ namespace Newtonsoft.Json.Utilities } /// - /// Gets the type of the typed list's items. + /// Gets the type of the typed collection's items. /// /// The type. - /// The type of the typed list's items. - public static Type GetListItemType(Type type) + /// The type of the typed collection's items. + public static Type GetCollectionItemType(Type type) { ValidationUtils.ArgumentNotNull(type, "type"); Type genericListType; @@ -194,10 +194,10 @@ namespace Newtonsoft.Json.Utilities { return type.GetElementType(); } - else if (IsSubClass(type, typeof(IList<>), out genericListType)) + else if (IsSubClass(type, typeof(ICollection<>), out genericListType)) { if (genericListType.IsGenericTypeDefinition) - throw new Exception("Type {0} is not a list.".FormatWith(CultureInfo.InvariantCulture, type)); + throw new Exception("Type {0} is not a collection.".FormatWith(CultureInfo.InvariantCulture, type)); return genericListType.GetGenericArguments()[0]; } @@ -207,7 +207,7 @@ namespace Newtonsoft.Json.Utilities } else { - throw new Exception("Type {0} is not a list.".FormatWith(CultureInfo.InvariantCulture, type)); + throw new Exception("Type {0} is not a collection.".FormatWith(CultureInfo.InvariantCulture, type)); } } @@ -266,7 +266,7 @@ namespace Newtonsoft.Json.Utilities { ValidationUtils.ArgumentNotNull(list, "list"); - Type elementType = GetListItemType(list.GetType()); + Type elementType = GetCollectionItemType(list.GetType()); if (elementType.IsValueType) {