From 3b26adfdd121e8861ae8a49f85014a3027b36cbb Mon Sep 17 00:00:00 2001 From: JamesNK Date: Sat, 9 May 2009 10:19:32 +0000 Subject: [PATCH] -Added support for DataContractAttribute and DataMemberAttribute to JsonSerializer -Added support for serializing private members by marking them with JsonPropertyAttribute or DataMemberAttribute --- Src/Newtonsoft.Json.Tests/JsonConvertTest.cs | 10 ++ .../JsonSerializerTest.cs | 146 ++++++++++++++++++ .../Newtonsoft.Json.Tests.Compact.csproj | 1 + .../CamelCaseMappingResolverTests.cs | 4 +- Src/Newtonsoft.Json.Tests/TestFixtureBase.cs | 5 + Src/Newtonsoft.Json/JsonSerializer.cs | 5 - .../Newtonsoft.Json.Compact.csproj | 1 + .../Newtonsoft.Json.Silverlight.csproj | 2 + Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 4 + .../Serialization/CachedAttributeGetter.cs | 34 ++++ .../Serialization/DefaultMappingResolver.cs | 76 +++++++-- .../Serialization/JsonTypeReflector.cs | 37 ++--- 12 files changed, 290 insertions(+), 35 deletions(-) create mode 100644 Src/Newtonsoft.Json/Serialization/CachedAttributeGetter.cs diff --git a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs index e90709da..0c5ecd9e 100644 --- a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs @@ -292,5 +292,15 @@ now brown cow?", '"', true); Assert.AreEqual(JsonConvert.NegativeInfinity, JsonConvert.ToString(double.NegativeInfinity)); Assert.AreEqual(JsonConvert.NaN, JsonConvert.ToString(double.NaN)); } + + [Test] + public void StringEscaping() + { + string v = @"It's a good day +""sunshine"""; + + string json = JsonConvert.ToString(v); + Assert.AreEqual(@"""It's a good day\r\n\""sunshine\""""", json); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs index 70a2c420..61d226ff 100644 --- a/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonSerializerTest.cs @@ -25,6 +25,9 @@ using System; using System.Collections.Generic; +#if !SILVERLIGHT && !PocketPC +using System.ComponentModel.DataAnnotations; +#endif using System.Text; using NUnit.Framework; using Newtonsoft.Json; @@ -40,6 +43,7 @@ using Newtonsoft.Json.Converters; using System.Runtime.Serialization.Json; #endif using Newtonsoft.Json.Tests.TestObjects; +using System.Runtime.Serialization; namespace Newtonsoft.Json.Tests { @@ -1471,5 +1475,147 @@ keyword such as type of business."" JsonConvert.SerializeObject(o, Formatting.Indented, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); } + + public class RequestOnly + { + public string Request { get; set; } + } + + public class NonRequest + { + public Guid Sid { get; set; } + public Guid Uid { get; set; } + public IList FidOrder { get; set; } + } + + [Test] + public void PartialClassDeserialize() + { + string json = @"{ + ""request"": ""ux.settings.update"", + ""sid"": ""14c561bd-32a8-457e-b4e5-4bba0832897f"", + ""uid"": ""30c39065-0f31-de11-9442-001e3786a8ec"", + ""fidOrder"": [ + ""id"", + ""andytest_name"", + ""andytest_age"", + ""andytest_address"", + ""andytest_phone"", + ""date"", + ""title"", + ""titleId"" + ], + ""entityName"": ""Andy Test"", + ""setting"": ""entity.field.order"" +}"; + + RequestOnly r = JsonConvert.DeserializeObject(json); + Assert.AreEqual("ux.settings.update", r.Request); + + NonRequest n = JsonConvert.DeserializeObject(json); + Assert.AreEqual(new Guid("14c561bd-32a8-457e-b4e5-4bba0832897f"), n.Sid); + Assert.AreEqual(new Guid("30c39065-0f31-de11-9442-001e3786a8ec"), n.Uid); + Assert.AreEqual(8, n.FidOrder.Count); + Assert.AreEqual("id", n.FidOrder[0]); + Assert.AreEqual("titleId", n.FidOrder[n.FidOrder.Count - 1]); + } + +#if !SILVERLIGHT && !PocketPC + [MetadataType(typeof(OptInClassMetadata))] + public class OptInClass + { + [DataContract] + public class OptInClassMetadata + { + [DataMember] + public string Name { get; set; } + [DataMember] + public int Age { get; set; } + public string NotIncluded { get; set; } + } + + public string Name { get; set; } + public int Age { get; set; } + public string NotIncluded { get; set; } + } + + [Test] + public void OptInClassMetadataSerialization() + { + OptInClass optInClass = new OptInClass(); + optInClass.Age = 26; + optInClass.Name = "James NK"; + optInClass.NotIncluded = "Poor me :("; + + string json = JsonConvert.SerializeObject(optInClass, Formatting.Indented); + + Assert.AreEqual(@"{ + ""Name"": ""James NK"", + ""Age"": 26 +}", json); + + OptInClass newOptInClass = JsonConvert.DeserializeObject(@"{ + ""Name"": ""James NK"", + ""NotIncluded"": ""Ignore me!"", + ""Age"": 26 +}"); + Assert.AreEqual(26, newOptInClass.Age); + Assert.AreEqual("James NK", newOptInClass.Name); + Assert.AreEqual(null, newOptInClass.NotIncluded); + } +#endif + +#if !PocketPC + [DataContract] + public class DataContractPrivateMembers + { + public DataContractPrivateMembers() + { + } + + public DataContractPrivateMembers(string name, int age, int rank, string title) + { + _name = name; + Age = age; + Rank = rank; + Title = title; + } + + [DataMember] + private string _name; + [DataMember(Name = "_age")] + private int Age { get; set; } + [JsonProperty] + private int Rank { get; set; } + [JsonProperty(PropertyName = "JsonTitle")] + [DataMember(Name = "DataTitle")] + private string Title { get; set; } + + public string NotIncluded { get; set; } + + public override string ToString() + { + return "_name: " + _name + ", _age: " + Age + ", Rank: " + Rank + ", JsonTitle: " + Title; + } + } + + [Test] + public void SerializeDataContractPrivateMembers() + { + DataContractPrivateMembers c = new DataContractPrivateMembers("Jeff", 26, 10, "Dr"); + c.NotIncluded = "Hi"; + string json = JsonConvert.SerializeObject(c, Formatting.Indented); + + Assert.AreEqual(@"{ + ""_name"": ""Jeff"", + ""_age"": 26, + ""Rank"": 10, + ""JsonTitle"": ""Dr"" +}", json); + + DataContractPrivateMembers cc = JsonConvert.DeserializeObject(json); + Assert.AreEqual("_name: Jeff, _age: 26, Rank: 10, JsonTitle: Dr", cc.ToString()); + } +#endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj index f78162da..288c3b8d 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj @@ -52,6 +52,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseMappingResolverTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseMappingResolverTests.cs index 1b2c6516..e3696120 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseMappingResolverTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseMappingResolverTests.cs @@ -96,7 +96,7 @@ namespace Newtonsoft.Json.Tests.Serialization string json = JsonConvert.SerializeObject(privateMembersClass, Formatting.Indented, new JsonSerializerSettings { - MappingResolver = new CamelCaseMappingResolver { MemberSearchFlags = BindingFlags.NonPublic | BindingFlags.Instance } + MappingResolver = new CamelCaseMappingResolver { DefaultMembersSearchFlags = BindingFlags.NonPublic | BindingFlags.Instance } }); Assert.AreEqual(@"{ @@ -111,7 +111,7 @@ namespace Newtonsoft.Json.Tests.Serialization ""_internalString"": ""Internal!"" }", new JsonSerializerSettings { - MappingResolver = new CamelCaseMappingResolver { MemberSearchFlags = BindingFlags.NonPublic | BindingFlags.Instance } + MappingResolver = new CamelCaseMappingResolver { DefaultMembersSearchFlags = BindingFlags.NonPublic | BindingFlags.Instance } }); Assert.AreEqual("Private!", ReflectionUtils.GetMemberValue(typeof(PrivateMembersClass).GetField("_privateString", BindingFlags.Instance | BindingFlags.NonPublic), deserializedPrivateMembersClass)); diff --git a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs index 0ba9f057..06eecdbe 100644 --- a/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs +++ b/Src/Newtonsoft.Json.Tests/TestFixtureBase.cs @@ -40,5 +40,10 @@ namespace Newtonsoft.Json.Tests // return utcOffset.Hours.ToString("+00;-00") + utcOffset.Minutes.ToString("00;00"); //} + + protected string EscapedJson(string json) + { + return @"@""" + json.Replace(@"""", @"""""") + @""""; + } } } diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index 2146fa2d..dfa25577 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -51,7 +51,6 @@ namespace Newtonsoft.Json private ObjectCreationHandling _objectCreationHandling; private NullValueHandling _nullValueHandling; private DefaultValueHandling _defaultValueHandling; - private int _level; private JsonConverterCollection _converters; private IMappingResolver _mappingResolver; @@ -258,8 +257,6 @@ namespace Newtonsoft.Json private object CreateObject(JsonReader reader, Type objectType, object existingValue, JsonConverter memberConverter) { - _level++; - object value; JsonConverter converter; @@ -343,8 +340,6 @@ namespace Newtonsoft.Json } } - _level--; - return value; } diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj index 2eacee4a..9be6a4f8 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj @@ -110,6 +110,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj index f072fe1b..d2ec8c66 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj @@ -39,6 +39,7 @@ Bin\Release\Newtonsoft.Json.Silverlight.XML + @@ -99,6 +100,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 161a2094..5bd4a071 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -76,6 +76,9 @@ 3.5 + + 3.0 + @@ -149,6 +152,7 @@ + diff --git a/Src/Newtonsoft.Json/Serialization/CachedAttributeGetter.cs b/Src/Newtonsoft.Json/Serialization/CachedAttributeGetter.cs new file mode 100644 index 00000000..878f6299 --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/CachedAttributeGetter.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Serialization +{ + internal static class CachedAttributeGetter where T : Attribute + { + private static readonly Dictionary TypeAttributeCache = new Dictionary(); + + public static T GetAttribute(ICustomAttributeProvider type) + { + T attribute; + + if (TypeAttributeCache.TryGetValue(type, out attribute)) + return attribute; + + // double check locking to avoid threading issues + lock (TypeAttributeCache) + { + if (TypeAttributeCache.TryGetValue(type, out attribute)) + return attribute; + + attribute = JsonTypeReflector.GetAttribute(type); + TypeAttributeCache[type] = attribute; + + return attribute; + } + } + } +} diff --git a/Src/Newtonsoft.Json/Serialization/DefaultMappingResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultMappingResolver.cs index ef2f3337..ebf8b3f2 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultMappingResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultMappingResolver.cs @@ -28,6 +28,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; using System.Text; using Newtonsoft.Json.Utilities; @@ -39,11 +40,11 @@ namespace Newtonsoft.Json.Serialization private readonly Dictionary TypeMemberMappingsCache = new Dictionary(); - public BindingFlags MemberSearchFlags { get; set; } + public BindingFlags DefaultMembersSearchFlags { get; set; } public DefaultMappingResolver() { - MemberSearchFlags = BindingFlags.Public | BindingFlags.Instance; + DefaultMembersSearchFlags = BindingFlags.Public | BindingFlags.Instance; } public virtual JsonMemberMappingCollection ResolveMappings(Type type) @@ -68,7 +69,32 @@ namespace Newtonsoft.Json.Serialization protected virtual List GetSerializableMembers(Type objectType) { - return ReflectionUtils.GetFieldsAndProperties(objectType, MemberSearchFlags); +#if !PocketPC + DataContractAttribute dataContractAttribute = JsonTypeReflector.GetDataContractAttribute(objectType); +#endif + + List defaultMembers = ReflectionUtils.GetFieldsAndProperties(objectType, DefaultMembersSearchFlags); + List allMembers = ReflectionUtils.GetFieldsAndProperties(objectType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + + List serializableMembers = new List(); + foreach (MemberInfo member in allMembers) + { + if (defaultMembers.Contains(member)) + { + serializableMembers.Add(member); + } + else + { + if (JsonTypeReflector.GetAttribute(member) != null) + serializableMembers.Add(member); +#if !PocketPC + else if (dataContractAttribute != null && JsonTypeReflector.GetAttribute(member) != null) + serializableMembers.Add(member); +#endif + } + } + + return serializableMembers; } private JsonMemberMappingCollection CreateMemberMappings(Type objectType) @@ -85,7 +111,8 @@ namespace Newtonsoft.Json.Serialization { JsonMemberMapping memberMapping = CreateMemberMapping(memberSerialization, member); - memberMappings.AddMapping(memberMapping); + if (memberMapping != null) + memberMappings.AddMapping(memberMapping); } return memberMappings; @@ -93,19 +120,48 @@ namespace Newtonsoft.Json.Serialization protected virtual JsonMemberMapping CreateMemberMapping(MemberSerialization memberSerialization, MemberInfo member) { +#if !PocketPC + DataContractAttribute dataContractAttribute = JsonTypeReflector.GetDataContractAttribute(member.DeclaringType); + + DataMemberAttribute dataMemberAttribute; + if (dataContractAttribute != null) + dataMemberAttribute = JsonTypeReflector.GetAttribute(member); + else + dataMemberAttribute = null; +#endif + JsonPropertyAttribute propertyAttribute = JsonTypeReflector.GetAttribute(member); bool hasIgnoreAttribute = (JsonTypeReflector.GetAttribute(member) != null); - string mappedName = (propertyAttribute != null && propertyAttribute.PropertyName != null) - ? propertyAttribute.PropertyName - : member.Name; + string mappedName; + if (propertyAttribute != null && propertyAttribute.PropertyName != null) + mappedName = propertyAttribute.PropertyName; +#if !PocketPC + else if (dataMemberAttribute != null && dataMemberAttribute.Name != null) + mappedName = dataMemberAttribute.Name; +#endif + else + mappedName = member.Name; string resolvedMappedName = ResolveMappingName(mappedName); - bool required = (propertyAttribute != null) ? propertyAttribute.IsRequired : false; + bool required; + if (propertyAttribute != null) + required = propertyAttribute.IsRequired; +#if !PocketPC + else if (dataMemberAttribute != null) + required = dataMemberAttribute.IsRequired; +#endif + else + required = false; - bool ignored = (hasIgnoreAttribute - || (memberSerialization == MemberSerialization.OptIn && propertyAttribute == null)); + bool ignored = (hasIgnoreAttribute || + (memberSerialization == MemberSerialization.OptIn + && propertyAttribute == null +#if !PocketPC + && dataMemberAttribute == null +#endif + )); bool readable = ReflectionUtils.CanReadMemberValue(member); bool writable = ReflectionUtils.CanSetMemberValue(member); diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index c29c381c..7f334f94 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -31,6 +31,7 @@ using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Serialization @@ -38,36 +39,36 @@ namespace Newtonsoft.Json.Serialization internal static class JsonTypeReflector { private static readonly Dictionary ConverterTypeCache = new Dictionary(); - private static readonly Dictionary TypeContainerAttributeCache = new Dictionary(); private static readonly Dictionary AssociatedMetadataTypesCache = new Dictionary(); public static JsonContainerAttribute GetJsonContainerAttribute(Type type) { - JsonContainerAttribute containerAttribute; - - if (TypeContainerAttributeCache.TryGetValue(type, out containerAttribute)) - return containerAttribute; - - // double check locking to avoid threading issues - lock (TypeContainerAttributeCache) - { - if (TypeContainerAttributeCache.TryGetValue(type, out containerAttribute)) - return containerAttribute; - - containerAttribute = ReflectionUtils.GetAttribute(type); - TypeContainerAttributeCache[type] = containerAttribute; - - return containerAttribute; - } + return CachedAttributeGetter.GetAttribute(type); } +#if !PocketPC + public static DataContractAttribute GetDataContractAttribute(Type type) + { + return CachedAttributeGetter.GetAttribute(type); + } +#endif + public static MemberSerialization GetObjectMemberSerialization(Type objectType) { JsonObjectAttribute objectAttribute = GetJsonContainerAttribute(objectType) as JsonObjectAttribute; if (objectAttribute == null) + { +#if !PocketPC + DataContractAttribute dataContractAttribute = GetDataContractAttribute(objectType); + + if (dataContractAttribute != null) + return MemberSerialization.OptIn; +#endif + return MemberSerialization.OptOut; - + } + return objectAttribute.MemberSerialization; }