diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj index 5dc30cae..648bf706 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj @@ -96,6 +96,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj index 1427dc21..d5fa425b 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj @@ -139,6 +139,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj index 7c6e598b..f0cdaacf 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj @@ -257,6 +257,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj index 0e7c701f..b39e015c 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj @@ -132,6 +132,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj index 4e3230e8..0b84de7d 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj @@ -136,6 +136,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 4d0de2a9..0d74f34c 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -262,6 +262,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseNamingStrategyTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseNamingStrategyTests.cs new file mode 100644 index 00000000..392933ea --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseNamingStrategyTests.cs @@ -0,0 +1,327 @@ +#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 Newtonsoft.Json.Serialization; +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#elif DNXCORE50 +using Xunit; +using Test = Xunit.FactAttribute; +using Assert = Newtonsoft.Json.Tests.XUnitAssert; +#else +using NUnit.Framework; +#endif +using Newtonsoft.Json.Tests.TestObjects; +using Newtonsoft.Json.Linq; +using System.Reflection; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Tests.Serialization +{ + [TestFixture] + public class CamelCaseNamingStrategyTests : TestFixtureBase + { + [Test] + public void JsonConvertSerializerSettings() + { + Person person = new Person(); + person.BirthDate = new DateTime(2000, 11, 20, 23, 55, 44, DateTimeKind.Utc); + person.LastModified = new DateTime(2000, 11, 20, 23, 55, 44, DateTimeKind.Utc); + person.Name = "Name!"; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + + string json = JsonConvert.SerializeObject(person, Formatting.Indented, new JsonSerializerSettings + { + ContractResolver = contractResolver + }); + + StringAssert.AreEqual(@"{ + ""name"": ""Name!"", + ""birthDate"": ""2000-11-20T23:55:44Z"", + ""lastModified"": ""2000-11-20T23:55:44Z"" +}", json); + + Person deserializedPerson = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + ContractResolver = contractResolver + }); + + Assert.AreEqual(person.BirthDate, deserializedPerson.BirthDate); + Assert.AreEqual(person.LastModified, deserializedPerson.LastModified); + Assert.AreEqual(person.Name, deserializedPerson.Name); + + json = JsonConvert.SerializeObject(person, Formatting.Indented); + StringAssert.AreEqual(@"{ + ""Name"": ""Name!"", + ""BirthDate"": ""2000-11-20T23:55:44Z"", + ""LastModified"": ""2000-11-20T23:55:44Z"" +}", json); + } + + [Test] + public void JTokenWriter_OverrideSpecifiedName() + { + JsonIgnoreAttributeOnClassTestClass ignoreAttributeOnClassTestClass = new JsonIgnoreAttributeOnClassTestClass(); + ignoreAttributeOnClassTestClass.Field = int.MinValue; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy + { + OverrideSpecifiedNames = true + } + }; + + JsonSerializer serializer = new JsonSerializer(); + serializer.ContractResolver = contractResolver; + + JTokenWriter writer = new JTokenWriter(); + + serializer.Serialize(writer, ignoreAttributeOnClassTestClass); + + JObject o = (JObject)writer.Token; + JProperty p = o.Property("theField"); + + Assert.IsNotNull(p); + Assert.AreEqual(int.MinValue, (int)p.Value); + } + + [Test] + public void BlogPostExample() + { + Product product = new Product + { + ExpiryDate = new DateTime(2010, 12, 20, 18, 1, 0, DateTimeKind.Utc), + Name = "Widget", + Price = 9.99m, + Sizes = new[] { "Small", "Medium", "Large" } + }; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + + string json = + JsonConvert.SerializeObject( + product, + Formatting.Indented, + new JsonSerializerSettings { ContractResolver = contractResolver } + ); + + //{ + // "name": "Widget", + // "expiryDate": "\/Date(1292868060000)\/", + // "price": 9.99, + // "sizes": [ + // "Small", + // "Medium", + // "Large" + // ] + //} + + StringAssert.AreEqual(@"{ + ""name"": ""Widget"", + ""expiryDate"": ""2010-12-20T18:01:00Z"", + ""price"": 9.99, + ""sizes"": [ + ""Small"", + ""Medium"", + ""Large"" + ] +}", json); + } + +#if !(NET35 || NET20 || PORTABLE40) + [Test] + public void DynamicCamelCasePropertyNames() + { + dynamic o = new TestDynamicObject(); + o.Text = "Text!"; + o.Integer = int.MaxValue; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy + { + CamelCaseDictionaryKeys = true + } + }; + + string json = JsonConvert.SerializeObject(o, Formatting.Indented, + new JsonSerializerSettings + { + ContractResolver = contractResolver + }); + + StringAssert.AreEqual(@"{ + ""explicit"": false, + ""text"": ""Text!"", + ""integer"": 2147483647, + ""int"": 0, + ""childObject"": null +}", json); + } +#endif + + [Test] + public void DictionaryCamelCasePropertyNames_Disabled() + { + Dictionary values = new Dictionary + { + { "First", "Value1!" }, + { "Second", "Value2!" } + }; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + + string json = JsonConvert.SerializeObject(values, Formatting.Indented, + new JsonSerializerSettings + { + ContractResolver = contractResolver + }); + + StringAssert.AreEqual(@"{ + ""First"": ""Value1!"", + ""Second"": ""Value2!"" +}", json); + } + + [Test] + public void DictionaryCamelCasePropertyNames_Enabled() + { + Dictionary values = new Dictionary + { + { "First", "Value1!" }, + { "Second", "Value2!" } + }; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy + { + CamelCaseDictionaryKeys = true + } + }; + + string json = JsonConvert.SerializeObject(values, Formatting.Indented, + new JsonSerializerSettings + { + ContractResolver = contractResolver + }); + + StringAssert.AreEqual(@"{ + ""first"": ""Value1!"", + ""second"": ""Value2!"" +}", json); + } + + public class PropertyAttributeNamingStrategyTestClass + { + [JsonProperty] + public string HasNoAttributeNamingStrategy { get; set; } + + [JsonProperty(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public string HasAttributeNamingStrategy { get; set; } + } + + [Test] + public void JsonPropertyAttribute_NamingStrategyType() + { + PropertyAttributeNamingStrategyTestClass c = new PropertyAttributeNamingStrategyTestClass + { + HasNoAttributeNamingStrategy = "Value1!", + HasAttributeNamingStrategy = "Value2!" + }; + + string json = JsonConvert.SerializeObject(c, Formatting.Indented); + + StringAssert.AreEqual(@"{ + ""HasNoAttributeNamingStrategy"": ""Value1!"", + ""hasAttributeNamingStrategy"": ""Value2!"" +}", json); + } + + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public class ContainerAttributeNamingStrategyTestClass + { + public string Prop1 { get; set; } + public string Prop2 { get; set; } + [JsonProperty(NamingStrategyType = typeof(DefaultNamingStrategy))] + public string HasAttributeNamingStrategy { get; set; } + } + + [Test] + public void JsonObjectAttribute_NamingStrategyType() + { + ContainerAttributeNamingStrategyTestClass c = new ContainerAttributeNamingStrategyTestClass + { + Prop1 = "Value1!", + Prop2 = "Value2!" + }; + + string json = JsonConvert.SerializeObject(c, Formatting.Indented); + + StringAssert.AreEqual(@"{ + ""prop1"": ""Value1!"", + ""prop2"": ""Value2!"", + ""HasAttributeNamingStrategy"": null +}", json); + } + + [JsonDictionary(NamingStrategyType = typeof(CamelCaseNamingStrategy), NamingStrategyParameters = new object[] { true, true })] + public class DictionaryAttributeNamingStrategyTestClass : Dictionary + { + } + + [Test] + public void JsonDictionaryAttribute_NamingStrategyType() + { + DictionaryAttributeNamingStrategyTestClass c = new DictionaryAttributeNamingStrategyTestClass + { + ["Key1"] = "Value1!", + ["Key2"] = "Value2!" + }; + + string json = JsonConvert.SerializeObject(c, Formatting.Indented); + + StringAssert.AreEqual(@"{ + ""key1"": ""Value1!"", + ""key2"": ""Value2!"" +}", json); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonContainerAttribute.cs b/Src/Newtonsoft.Json/JsonContainerAttribute.cs index cfc1b58f..bf894657 100644 --- a/Src/Newtonsoft.Json/JsonContainerAttribute.cs +++ b/Src/Newtonsoft.Json/JsonContainerAttribute.cs @@ -24,6 +24,7 @@ #endregion using System; +using Newtonsoft.Json.Serialization; namespace Newtonsoft.Json { @@ -68,12 +69,36 @@ namespace Newtonsoft.Json /// public object[] ItemConverterParameters { get; set; } + public Type NamingStrategyType + { + get { return _namingStrategyType; } + set + { + _namingStrategyType = value; + NamingStrategy = null; + } + } + + public object[] NamingStrategyParameters + { + get { return _namingStrategyParameters; } + set + { + _namingStrategyParameters = value; + NamingStrategy = null; + } + } + + internal INamingStrategy NamingStrategy { 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; internal bool? _itemIsReference; internal ReferenceLoopHandling? _itemReferenceLoopHandling; internal TypeNameHandling? _itemTypeNameHandling; + private Type _namingStrategyType; + private object[] _namingStrategyParameters; /// /// Gets or sets a value that indicates whether to preserve object references. diff --git a/Src/Newtonsoft.Json/JsonPropertyAttribute.cs b/Src/Newtonsoft.Json/JsonPropertyAttribute.cs index 6efe0ed7..211bd842 100644 --- a/Src/Newtonsoft.Json/JsonPropertyAttribute.cs +++ b/Src/Newtonsoft.Json/JsonPropertyAttribute.cs @@ -64,6 +64,9 @@ namespace Newtonsoft.Json /// public object[] ItemConverterParameters { get; set; } + public Type NamingStrategyType { get; set; } + public object[] NamingStrategyParameters { get; set; } + /// /// Gets or sets the null value handling used when serializing this property. /// diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index b63455d0..768041e0 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -128,9 +128,12 @@ + + + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index 09ea61c9..1330dc52 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -142,9 +142,12 @@ + + + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj index e4cf0e32..f78c0ef7 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj @@ -141,9 +141,12 @@ + + + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj index 2ff19dd6..963f2d2f 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj @@ -164,7 +164,9 @@ + + @@ -175,6 +177,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj index 762db4d0..9ff94ac4 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj @@ -169,8 +169,10 @@ + + @@ -180,6 +182,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index d69978da..363c7dda 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -168,8 +168,10 @@ + + @@ -179,6 +181,7 @@ + diff --git a/Src/Newtonsoft.Json/Serialization/CamelCaseNamingStrategy.cs b/Src/Newtonsoft.Json/Serialization/CamelCaseNamingStrategy.cs new file mode 100644 index 00000000..a2d8a8c8 --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/CamelCaseNamingStrategy.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Serialization +{ + public class CamelCaseNamingStrategy : INamingStrategy + { + public bool CamelCaseDictionaryKeys { get; set; } + public bool OverrideSpecifiedNames { get; set; } + + public CamelCaseNamingStrategy() + { + } + + public CamelCaseNamingStrategy(bool camelCaseDictionaryKeys, bool overrideSpecifiedNames) + { + CamelCaseDictionaryKeys = camelCaseDictionaryKeys; + OverrideSpecifiedNames = overrideSpecifiedNames; + } + + public string GetPropertyName(string s, bool hasSpecifiedName) + { + if (hasSpecifiedName && !OverrideSpecifiedNames) + { + return s; + } + + return StringUtils.ToCamelCase(s); + } + + public string GetDictionaryKey(string s) + { + if (!CamelCaseDictionaryKeys) + { + return s; + } + + return StringUtils.ToCamelCase(s); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs b/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs index ea9ee82d..42061260 100644 --- a/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs @@ -41,17 +41,11 @@ namespace Newtonsoft.Json.Serialization : base(true) #pragma warning restore 612,618 { - } - - /// - /// Resolves the name of the property. - /// - /// Name of the property. - /// The property name camel cased. - protected override string ResolvePropertyName(string propertyName) - { - // lower case the first letter (or more) of the passed in name - return StringUtils.ToCamelCase(propertyName); + NamingStrategy = new CamelCaseNamingStrategy + { + CamelCaseDictionaryKeys = true, + OverrideSpecifiedNames = true + }; } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index bf970338..3c0b769e 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -185,6 +185,8 @@ namespace Newtonsoft.Json.Serialization public bool IgnoreSerializableAttribute { get; set; } #endif + public INamingStrategy NamingStrategy { get; set; } + /// /// Initializes a new instance of the class. /// @@ -738,7 +740,6 @@ namespace Newtonsoft.Json.Serialization #if NET35 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Portability", "CA1903:UseOnlyApiFromTargetedFramework", MessageId = "System.Runtime.Serialization.DataContractAttribute.#get_IsReference()")] #endif - private void InitializeContract(JsonContract contract) { JsonContainerAttribute containerAttribute = JsonTypeReflector.GetCachedAttribute(contract.NonNullableUnderlyingType); @@ -906,7 +907,9 @@ namespace Newtonsoft.Json.Serialization #endif #if DOTNET if (t.IsGenericType() && t.GetGenericTypeDefinition() == typeof(ConcurrentDictionary<,>)) + { return true; + } #endif return false; @@ -938,7 +941,16 @@ namespace Newtonsoft.Json.Serialization JsonDictionaryContract contract = new JsonDictionaryContract(objectType); InitializeContract(contract); - contract.DictionaryKeyResolver = ResolveDictionaryKey; + JsonContainerAttribute containerAttribute = JsonTypeReflector.GetAttribute(objectType); + if (containerAttribute?.NamingStrategyType != null) + { + INamingStrategy namingStrategy = JsonTypeReflector.GetContainerNamingStrategy(containerAttribute); + contract.DictionaryKeyResolver = s => namingStrategy.GetDictionaryKey(s); + } + else + { + contract.DictionaryKeyResolver = ResolveDictionaryKey; + } ConstructorInfo overrideConstructor = GetAttributeConstructor(contract.NonNullableUnderlyingType); @@ -1066,7 +1078,17 @@ namespace Newtonsoft.Json.Serialization JsonDynamicContract contract = new JsonDynamicContract(objectType); InitializeContract(contract); - contract.PropertyNameResolver = ResolvePropertyName; + JsonContainerAttribute containerAttribute = JsonTypeReflector.GetAttribute(objectType); + if (containerAttribute?.NamingStrategyType != null) + { + INamingStrategy namingStrategy = JsonTypeReflector.GetContainerNamingStrategy(containerAttribute); + contract.PropertyNameResolver = s => namingStrategy.GetDictionaryKey(s); + } + else + { + contract.PropertyNameResolver = ResolveDictionaryKey; + } + contract.Properties.AddRange(CreateProperties(objectType, MemberSerialization.OptOut)); return contract; @@ -1387,22 +1409,50 @@ namespace Newtonsoft.Json.Serialization JsonRequiredAttribute requiredAttribute = JsonTypeReflector.GetAttribute(attributeProvider); string mappedName; + bool hasSpecifiedName; if (propertyAttribute != null && propertyAttribute.PropertyName != null) { mappedName = propertyAttribute.PropertyName; + hasSpecifiedName = true; } #if !NET20 else if (dataMemberAttribute != null && dataMemberAttribute.Name != null) { mappedName = dataMemberAttribute.Name; + hasSpecifiedName = true; } #endif else { mappedName = name; + hasSpecifiedName = false; } - property.PropertyName = ResolvePropertyName(mappedName); + JsonContainerAttribute containerAttribute = JsonTypeReflector.GetAttribute(declaringType); + + INamingStrategy namingStrategy; + if (propertyAttribute?.NamingStrategyType != null) + { + namingStrategy = JsonTypeReflector.CreateNamingStrategyInstance(propertyAttribute.NamingStrategyType, propertyAttribute.NamingStrategyParameters); + } + else if (containerAttribute?.NamingStrategyType != null) + { + namingStrategy = JsonTypeReflector.GetContainerNamingStrategy(containerAttribute); + } + else + { + namingStrategy = NamingStrategy; + } + + if (namingStrategy != null) + { + property.PropertyName = namingStrategy.GetPropertyName(mappedName, hasSpecifiedName); + } + else + { + property.PropertyName = ResolvePropertyName(mappedName); + } + property.UnderlyingName = name; bool hasMemberAttribute = false; @@ -1543,6 +1593,11 @@ namespace Newtonsoft.Json.Serialization /// Resolved name of the property. protected virtual string ResolvePropertyName(string propertyName) { + if (NamingStrategy != null) + { + return NamingStrategy.GetPropertyName(propertyName, false); + } + return propertyName; } @@ -1553,6 +1608,11 @@ namespace Newtonsoft.Json.Serialization /// Resolved key of the dictionary. protected virtual string ResolveDictionaryKey(string dictionaryKey) { + if (NamingStrategy != null) + { + return NamingStrategy.GetDictionaryKey(dictionaryKey); + } + return ResolvePropertyName(dictionaryKey); } diff --git a/Src/Newtonsoft.Json/Serialization/DefaultNamingStrategy.cs b/Src/Newtonsoft.Json/Serialization/DefaultNamingStrategy.cs new file mode 100644 index 00000000..2ddec384 --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/DefaultNamingStrategy.cs @@ -0,0 +1,15 @@ +namespace Newtonsoft.Json.Serialization +{ + public class DefaultNamingStrategy : INamingStrategy + { + public string GetPropertyName(string s, bool hasSpecifiedName) + { + return s; + } + + public string GetDictionaryKey(string s) + { + return s; + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/INamingStrategy.cs b/Src/Newtonsoft.Json/Serialization/INamingStrategy.cs new file mode 100644 index 00000000..3767be6f --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/INamingStrategy.cs @@ -0,0 +1,8 @@ +namespace Newtonsoft.Json.Serialization +{ + public interface INamingStrategy + { + string GetPropertyName(string s, bool hasSpecifiedName); + string GetDictionaryKey(string s); + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index 0744b943..ac649dc5 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -56,8 +56,8 @@ namespace Newtonsoft.Json.Serialization public const string ShouldSerializePrefix = "ShouldSerialize"; public const string SpecifiedPostfix = "Specified"; - private static readonly ThreadSafeStore> JsonConverterCreatorCache = - new ThreadSafeStore>(GetJsonConverterCreator); + private static readonly ThreadSafeStore> CreatorCache = + new ThreadSafeStore>(GetCreator); #if !(NET20 || DOTNET) private static readonly ThreadSafeStore AssociatedMetadataTypesCache = new ThreadSafeStore(GetAssociateMetadataTypeFromAttribute); @@ -162,10 +162,10 @@ namespace Newtonsoft.Json.Serialization if (converterAttribute != null) { - Func creator = JsonConverterCreatorCache.Get(converterAttribute.ConverterType); + Func creator = CreatorCache.Get(converterAttribute.ConverterType); if (creator != null) { - return creator(converterAttribute.ConverterParameters); + return (JsonConverter)creator(converterAttribute.ConverterParameters); } } @@ -180,19 +180,36 @@ namespace Newtonsoft.Json.Serialization /// If null, the default constructor is used. public static JsonConverter CreateJsonConverterInstance(Type converterType, object[] converterArgs) { - Func converterCreator = JsonConverterCreatorCache.Get(converterType); - return converterCreator(converterArgs); + Func converterCreator = CreatorCache.Get(converterType); + return (JsonConverter)converterCreator(converterArgs); } - /// - /// Create a factory function that can be used to create instances of a JsonConverter described by the - /// argument type. The returned function can then be used to either invoke the converter's default ctor, or any - /// parameterized constructors by way of an object array. - /// - private static Func GetJsonConverterCreator(Type converterType) + public static INamingStrategy CreateNamingStrategyInstance(Type namingStrategyType, object[] converterArgs) { - Func defaultConstructor = (ReflectionUtils.HasDefaultConstructor(converterType, false)) - ? ReflectionDelegateFactory.CreateDefaultConstructor(converterType) + Func converterCreator = CreatorCache.Get(namingStrategyType); + return (INamingStrategy)converterCreator(converterArgs); + } + + public static INamingStrategy GetContainerNamingStrategy(JsonContainerAttribute containerAttribute) + { + if (containerAttribute.NamingStrategy == null) + { + if (containerAttribute.NamingStrategyType == null) + { + return null; + + } + + containerAttribute.NamingStrategy = CreateNamingStrategyInstance(containerAttribute.NamingStrategyType, containerAttribute.NamingStrategyParameters); + } + + return containerAttribute.NamingStrategy; + } + + private static Func GetCreator(Type type) + { + Func defaultConstructor = (ReflectionUtils.HasDefaultConstructor(type, false)) + ? ReflectionDelegateFactory.CreateDefaultConstructor(type) : null; return (parameters) => @@ -201,31 +218,30 @@ namespace Newtonsoft.Json.Serialization { if (parameters != null) { - ObjectConstructor parameterizedConstructor = null; Type[] paramTypes = parameters.Select(param => param.GetType()).ToArray(); - ConstructorInfo parameterizedConstructorInfo = converterType.GetConstructor(paramTypes); + ConstructorInfo parameterizedConstructorInfo = type.GetConstructor(paramTypes); if (null != parameterizedConstructorInfo) { - parameterizedConstructor = ReflectionDelegateFactory.CreateParameterizedConstructor(parameterizedConstructorInfo); - return (JsonConverter)parameterizedConstructor(parameters); + ObjectConstructor parameterizedConstructor = ReflectionDelegateFactory.CreateParameterizedConstructor(parameterizedConstructorInfo); + return parameterizedConstructor(parameters); } else { - throw new JsonException("No matching parameterized constructor found for '{0}'.".FormatWith(CultureInfo.InvariantCulture, converterType)); + throw new JsonException("No matching parameterized constructor found for '{0}'.".FormatWith(CultureInfo.InvariantCulture, type)); } } if (defaultConstructor == null) { - throw new JsonException("No parameterless constructor defined for '{0}'.".FormatWith(CultureInfo.InvariantCulture, converterType)); + throw new JsonException("No parameterless constructor defined for '{0}'.".FormatWith(CultureInfo.InvariantCulture, type)); } - return (JsonConverter)defaultConstructor(); + return defaultConstructor(); } catch (Exception ex) { - throw new JsonException("Error creating '{0}'.".FormatWith(CultureInfo.InvariantCulture, converterType), ex); + throw new JsonException("Error creating '{0}'.".FormatWith(CultureInfo.InvariantCulture, type), ex); } }; }