diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj index 242d860f..4fa4587a 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 @@ + @@ -112,6 +113,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net35.csproj index c848d910..a1f79940 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 @@ + @@ -155,6 +156,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net40.csproj index 71333592..e9635d0e 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 @@ + @@ -271,6 +272,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable.csproj index 83b2bd64..2be23499 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 @@ + @@ -146,6 +147,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Portable40.csproj index 92d71a86..2eda8590 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 @@ + @@ -150,6 +151,7 @@ + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 4ec9338d..5eb93ab5 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -262,6 +262,8 @@ + + diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 7fdd7ec7..475cc910 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -503,7 +503,7 @@ If attributes are not mentioned, default values are used in each case. { for (int i = 0; i < interations; i++) { - using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(text) ?? 16)) + using (StringWriter w = StringUtils.CreateStringWriter(text.Length)) { char[] buffer = null; JavaScriptUtils.WriteEscapedJavaScriptString(w, text, '"', true, JavaScriptUtils.DoubleQuoteCharEscapeFlags, StringEscapeHandling.Default, null, ref buffer); diff --git a/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseNamingStrategyTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/CamelCaseNamingStrategyTests.cs new file mode 100644 index 00000000..d0822a3b --- /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 + { + ProcessDictionaryKeys = 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 + { + ProcessDictionaryKeys = 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.Tests/Serialization/SnakeCaseNamingStrategyTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/SnakeCaseNamingStrategyTests.cs new file mode 100644 index 00000000..9c254b7d --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Serialization/SnakeCaseNamingStrategyTests.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 SnakeCaseNamingStrategyTests : 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 SnakeCaseNamingStrategy() + }; + + string json = JsonConvert.SerializeObject(person, Formatting.Indented, new JsonSerializerSettings + { + ContractResolver = contractResolver + }); + + StringAssert.AreEqual(@"{ + ""name"": ""Name!"", + ""birth_date"": ""2000-11-20T23:55:44Z"", + ""last_modified"": ""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 SnakeCaseNamingStrategy + { + 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("the_field"); + + 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 SnakeCaseNamingStrategy() + }; + + 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"", + ""expiry_date"": ""2010-12-20T18:01:00Z"", + ""price"": 9.99, + ""sizes"": [ + ""Small"", + ""Medium"", + ""Large"" + ] +}", json); + } + +#if !(NET35 || NET20 || PORTABLE40) + [Test] + public void DynamicSnakeCasePropertyNames() + { + dynamic o = new TestDynamicObject(); + o.Text = "Text!"; + o.Integer = int.MaxValue; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy + { + ProcessDictionaryKeys = true + } + }; + + string json = JsonConvert.SerializeObject(o, Formatting.Indented, + new JsonSerializerSettings + { + ContractResolver = contractResolver + }); + + StringAssert.AreEqual(@"{ + ""explicit"": false, + ""text"": ""Text!"", + ""integer"": 2147483647, + ""int"": 0, + ""child_object"": null +}", json); + } +#endif + + [Test] + public void DictionarySnakeCasePropertyNames_Disabled() + { + Dictionary values = new Dictionary + { + { "First", "Value1!" }, + { "Second", "Value2!" } + }; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + }; + + string json = JsonConvert.SerializeObject(values, Formatting.Indented, + new JsonSerializerSettings + { + ContractResolver = contractResolver + }); + + StringAssert.AreEqual(@"{ + ""First"": ""Value1!"", + ""Second"": ""Value2!"" +}", json); + } + + [Test] + public void DictionarySnakeCasePropertyNames_Enabled() + { + Dictionary values = new Dictionary + { + { "First", "Value1!" }, + { "Second", "Value2!" } + }; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy + { + ProcessDictionaryKeys = 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(SnakeCaseNamingStrategy))] + 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!"", + ""has_attribute_naming_strategy"": ""Value2!"" +}", json); + } + + [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] + 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(SnakeCaseNamingStrategy), 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.Tests/Utilities/StringUtilsTests.cs b/Src/Newtonsoft.Json.Tests/Utilities/StringUtilsTests.cs index 893776b1..bdc8e7e6 100644 --- a/Src/Newtonsoft.Json.Tests/Utilities/StringUtilsTests.cs +++ b/Src/Newtonsoft.Json.Tests/Utilities/StringUtilsTests.cs @@ -50,14 +50,53 @@ namespace Newtonsoft.Json.Tests.Utilities Assert.AreEqual("i", StringUtils.ToCamelCase("I")); Assert.AreEqual("", StringUtils.ToCamelCase("")); Assert.AreEqual(null, StringUtils.ToCamelCase(null)); - Assert.AreEqual("iPhone", StringUtils.ToCamelCase("iPhone")); Assert.AreEqual("person", StringUtils.ToCamelCase("Person")); + Assert.AreEqual("iPhone", StringUtils.ToCamelCase("iPhone")); Assert.AreEqual("iPhone", StringUtils.ToCamelCase("IPhone")); Assert.AreEqual("i Phone", StringUtils.ToCamelCase("I Phone")); + Assert.AreEqual("i Phone", StringUtils.ToCamelCase("I Phone")); Assert.AreEqual(" IPhone", StringUtils.ToCamelCase(" IPhone")); + Assert.AreEqual(" IPhone ", StringUtils.ToCamelCase(" IPhone ")); Assert.AreEqual("isCIA", StringUtils.ToCamelCase("IsCIA")); Assert.AreEqual("vmQ", StringUtils.ToCamelCase("VmQ")); Assert.AreEqual("xml2Json", StringUtils.ToCamelCase("Xml2Json")); + Assert.AreEqual("snAkEcAsE", StringUtils.ToCamelCase("SnAkEcAsE")); + Assert.AreEqual("snA__kEcAsE", StringUtils.ToCamelCase("SnA__kEcAsE")); + Assert.AreEqual("snA__ kEcAsE", StringUtils.ToCamelCase("SnA__ kEcAsE")); + Assert.AreEqual("already_snake_case_ ", StringUtils.ToCamelCase("already_snake_case_ ")); + Assert.AreEqual("isJSONProperty", StringUtils.ToCamelCase("IsJSONProperty")); + Assert.AreEqual("shoutinG_CASE", StringUtils.ToCamelCase("SHOUTING_CASE")); + Assert.AreEqual("9999-12-31T23:59:59.9999999Z", StringUtils.ToCamelCase("9999-12-31T23:59:59.9999999Z")); + Assert.AreEqual("hi!! This is text. Time to test.", StringUtils.ToCamelCase("Hi!! This is text. Time to test.")); + } + + [Test] + public void ToSnakeCaseTest() + { + Assert.AreEqual("url_value", StringUtils.ToSnakeCase("URLValue")); + Assert.AreEqual("url", StringUtils.ToSnakeCase("URL")); + Assert.AreEqual("id", StringUtils.ToSnakeCase("ID")); + Assert.AreEqual("i", StringUtils.ToSnakeCase("I")); + Assert.AreEqual("", StringUtils.ToSnakeCase("")); + Assert.AreEqual(null, StringUtils.ToSnakeCase(null)); + Assert.AreEqual("person", StringUtils.ToSnakeCase("Person")); + Assert.AreEqual("i_phone", StringUtils.ToSnakeCase("iPhone")); + Assert.AreEqual("i_phone", StringUtils.ToSnakeCase("IPhone")); + Assert.AreEqual("i_phone", StringUtils.ToSnakeCase("I Phone")); + Assert.AreEqual("i_phone", StringUtils.ToSnakeCase("I Phone")); + Assert.AreEqual("i_phone", StringUtils.ToSnakeCase(" IPhone")); + Assert.AreEqual("i_phone", StringUtils.ToSnakeCase(" IPhone ")); + Assert.AreEqual("is_cia", StringUtils.ToSnakeCase("IsCIA")); + Assert.AreEqual("vm_q", StringUtils.ToSnakeCase("VmQ")); + Assert.AreEqual("xml2_json", StringUtils.ToSnakeCase("Xml2Json")); + Assert.AreEqual("sn_ak_ec_as_e", StringUtils.ToSnakeCase("SnAkEcAsE")); + Assert.AreEqual("sn_a__k_ec_as_e", StringUtils.ToSnakeCase("SnA__kEcAsE")); + Assert.AreEqual("sn_a__k_ec_as_e", StringUtils.ToSnakeCase("SnA__ kEcAsE")); + Assert.AreEqual("already_snake_case_", StringUtils.ToSnakeCase("already_snake_case_ ")); + Assert.AreEqual("is_json_property", StringUtils.ToSnakeCase("IsJSONProperty")); + Assert.AreEqual("shouting_case", StringUtils.ToSnakeCase("SHOUTING_CASE")); + Assert.AreEqual("9999-12-31_t23:59:59.9999999_z", StringUtils.ToSnakeCase("9999-12-31T23:59:59.9999999Z")); + Assert.AreEqual("hi!!_this_is_text._time_to_test.", StringUtils.ToSnakeCase("Hi!! This is text. Time to test.")); } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs b/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs index 9d2f822a..ceb38797 100644 --- a/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/IsoDateTimeConverter.cs @@ -57,7 +57,7 @@ namespace Newtonsoft.Json.Converters public string DateTimeFormat { get { return _dateTimeFormat ?? string.Empty; } - set { _dateTimeFormat = StringUtils.NullEmptyString(value); } + set { _dateTimeFormat = (string.IsNullOrEmpty(value)) ? null : value; } } /// diff --git a/Src/Newtonsoft.Json/JsonContainerAttribute.cs b/Src/Newtonsoft.Json/JsonContainerAttribute.cs index cfc1b58f..32bd975e 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 { @@ -52,15 +53,15 @@ namespace Newtonsoft.Json public string Description { get; set; } /// - /// Gets the collection's items converter. + /// Gets or sets the collection's items converter. /// /// The collection's items converter. public Type ItemConverterType { get; set; } /// - /// The parameter list to use when constructing the JsonConverter described by ItemConverterType. + /// The parameter list to use when constructing the described by ItemConverterType. /// If null, the default constructor is used. - /// When non-null, there must be a constructor defined in the JsonConverter that exactly matches the number, + /// When non-null, there must be a constructor defined in the that exactly matches the number, /// order, and type of these parameters. /// /// @@ -68,12 +69,49 @@ namespace Newtonsoft.Json /// public object[] ItemConverterParameters { get; set; } + /// + /// Gets or sets the of the . + /// + /// The of the . + public Type NamingStrategyType + { + get { return _namingStrategyType; } + set + { + _namingStrategyType = value; + NamingStrategyInstance = null; + } + } + + /// + /// The parameter list to use when constructing the described by NamingStrategyType. + /// If null, the default constructor is used. + /// When non-null, there must be a constructor defined in the that exactly matches the number, + /// order, and type of these parameters. + /// + /// + /// [JsonContainer(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] + /// + public object[] NamingStrategyParameters + { + get { return _namingStrategyParameters; } + set + { + _namingStrategyParameters = value; + NamingStrategyInstance = null; + } + } + + internal NamingStrategy NamingStrategyInstance { 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/JsonConverterAttribute.cs b/Src/Newtonsoft.Json/JsonConverterAttribute.cs index fdafb3a4..aea831f6 100644 --- a/Src/Newtonsoft.Json/JsonConverterAttribute.cs +++ b/Src/Newtonsoft.Json/JsonConverterAttribute.cs @@ -38,16 +38,16 @@ namespace Newtonsoft.Json private readonly Type _converterType; /// - /// Gets the of the converter. + /// Gets the of the . /// - /// The of the converter. + /// The of the . public Type ConverterType { get { return _converterType; } } /// - /// The parameter list to use when constructing the JsonConverter described by ConverterType. + /// The parameter list to use when constructing the described by ConverterType. /// If null, the default constructor is used. /// public object[] ConverterParameters { get; private set; } @@ -55,7 +55,7 @@ namespace Newtonsoft.Json /// /// Initializes a new instance of the class. /// - /// Type of the converter. + /// Type of the . public JsonConverterAttribute(Type converterType) { if (converterType == null) @@ -69,8 +69,8 @@ namespace Newtonsoft.Json /// /// Initializes a new instance of the class. /// - /// Type of the converter. - /// Parameter list to use when constructing the JsonConverter. Can be null. + /// Type of the . + /// Parameter list to use when constructing the . Can be null. public JsonConverterAttribute(Type converterType, params object[] converterParameters) : this(converterType) { diff --git a/Src/Newtonsoft.Json/JsonPropertyAttribute.cs b/Src/Newtonsoft.Json/JsonPropertyAttribute.cs index 6efe0ed7..0252ee44 100644 --- a/Src/Newtonsoft.Json/JsonPropertyAttribute.cs +++ b/Src/Newtonsoft.Json/JsonPropertyAttribute.cs @@ -24,6 +24,7 @@ #endregion using System; +using Newtonsoft.Json.Serialization; namespace Newtonsoft.Json { @@ -48,15 +49,15 @@ namespace Newtonsoft.Json internal TypeNameHandling? _itemTypeNameHandling; /// - /// Gets or sets the converter used when serializing the property's collection items. + /// Gets or sets the used when serializing the property's collection items. /// - /// The collection's items converter. + /// The collection's items . public Type ItemConverterType { get; set; } /// - /// The parameter list to use when constructing the JsonConverter described by ItemConverterType. + /// The parameter list to use when constructing the described by ItemConverterType. /// If null, the default constructor is used. - /// When non-null, there must be a constructor defined in the JsonConverter that exactly matches the number, + /// When non-null, there must be a constructor defined in the that exactly matches the number, /// order, and type of these parameters. /// /// @@ -64,6 +65,23 @@ namespace Newtonsoft.Json /// public object[] ItemConverterParameters { get; set; } + /// + /// Gets or sets the of the . + /// + /// The of the . + public Type NamingStrategyType { get; set; } + + /// + /// The parameter list to use when constructing the described by NamingStrategyType. + /// If null, the default constructor is used. + /// When non-null, there must be a constructor defined in the that exactly matches the number, + /// order, and type of these parameters. + /// + /// + /// [JsonProperty(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] + /// + 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..436a3683 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -128,6 +128,8 @@ + + @@ -135,7 +137,9 @@ + + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index 09ea61c9..697a958c 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -142,13 +142,17 @@ + + + + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj index e4cf0e32..2e41fe86 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj @@ -141,13 +141,17 @@ + + + + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj index 2ff19dd6..56f6b0ce 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj @@ -164,7 +164,9 @@ + + @@ -197,10 +199,12 @@ + + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj index 762db4d0..55c3494a 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj @@ -169,8 +169,10 @@ + + @@ -202,10 +204,12 @@ + + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index d69978da..1055878b 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -168,8 +168,11 @@ + + + @@ -179,6 +182,7 @@ + diff --git a/Src/Newtonsoft.Json/Serialization/CamelCaseNamingStrategy.cs b/Src/Newtonsoft.Json/Serialization/CamelCaseNamingStrategy.cs new file mode 100644 index 00000000..80d1b9ac --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/CamelCaseNamingStrategy.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Serialization +{ + /// + /// A camel case naming strategy. + /// + public class CamelCaseNamingStrategy : NamingStrategy + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A flag indicating whether dictionary keys should be processed. + /// + /// + /// A flag indicating whether explicitly specified property names should be processed, + /// e.g. a property name customized with a . + /// + public CamelCaseNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames) + { + ProcessDictionaryKeys = processDictionaryKeys; + OverrideSpecifiedNames = overrideSpecifiedNames; + } + + /// + /// Initializes a new instance of the class. + /// + public CamelCaseNamingStrategy() + { + } + + /// + /// Resolves the specified property name. + /// + /// The property name to resolve. + /// The resolved property name. + protected override string ResolvePropertyName(string name) + { + return StringUtils.ToCamelCase(name); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs b/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs index ea9ee82d..46085d58 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 + { + ProcessDictionaryKeys = 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 52c7b819..43a19c3e 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -184,6 +184,12 @@ namespace Newtonsoft.Json.Serialization public bool IgnoreSerializableAttribute { get; set; } #endif + /// + /// Gets or sets the naming strategy used to resolve how property names and dictionary keys are serialized. + /// + /// The naming strategy used to resolve how property names and dictionary keys are serialized. + public NamingStrategy NamingStrategy { get; set; } + /// /// Initializes a new instance of the class. /// @@ -737,7 +743,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); @@ -905,7 +910,9 @@ namespace Newtonsoft.Json.Serialization #endif #if DOTNET if (t.IsGenericType() && t.GetGenericTypeDefinition() == typeof(ConcurrentDictionary<,>)) + { return true; + } #endif return false; @@ -937,7 +944,16 @@ namespace Newtonsoft.Json.Serialization JsonDictionaryContract contract = new JsonDictionaryContract(objectType); InitializeContract(contract); - contract.DictionaryKeyResolver = ResolveDictionaryKey; + JsonContainerAttribute containerAttribute = JsonTypeReflector.GetAttribute(objectType); + if (containerAttribute?.NamingStrategyType != null) + { + NamingStrategy namingStrategy = JsonTypeReflector.GetContainerNamingStrategy(containerAttribute); + contract.DictionaryKeyResolver = s => namingStrategy.GetDictionaryKey(s); + } + else + { + contract.DictionaryKeyResolver = ResolveDictionaryKey; + } ConstructorInfo overrideConstructor = GetAttributeConstructor(contract.NonNullableUnderlyingType); @@ -1065,7 +1081,17 @@ namespace Newtonsoft.Json.Serialization JsonDynamicContract contract = new JsonDynamicContract(objectType); InitializeContract(contract); - contract.PropertyNameResolver = ResolvePropertyName; + JsonContainerAttribute containerAttribute = JsonTypeReflector.GetAttribute(objectType); + if (containerAttribute?.NamingStrategyType != null) + { + NamingStrategy namingStrategy = JsonTypeReflector.GetContainerNamingStrategy(containerAttribute); + contract.PropertyNameResolver = s => namingStrategy.GetDictionaryKey(s); + } + else + { + contract.PropertyNameResolver = ResolveDictionaryKey; + } + contract.Properties.AddRange(CreateProperties(objectType, MemberSerialization.OptOut)); return contract; @@ -1386,22 +1412,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); + + NamingStrategy 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; @@ -1542,6 +1596,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; } @@ -1552,6 +1611,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..0f60780b --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/DefaultNamingStrategy.cs @@ -0,0 +1,18 @@ +namespace Newtonsoft.Json.Serialization +{ + /// + /// The default naming strategy. Property names and dictionary keys are unchanged. + /// + public class DefaultNamingStrategy : NamingStrategy + { + /// + /// Resolves the specified property name. + /// + /// The property name to resolve. + /// The resolved property name. + protected override string ResolvePropertyName(string name) + { + return name; + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index 0744b943..ae1b5349 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,35 @@ 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 NamingStrategy CreateNamingStrategyInstance(Type namingStrategyType, object[] converterArgs) { - Func defaultConstructor = (ReflectionUtils.HasDefaultConstructor(converterType, false)) - ? ReflectionDelegateFactory.CreateDefaultConstructor(converterType) + Func converterCreator = CreatorCache.Get(namingStrategyType); + return (NamingStrategy)converterCreator(converterArgs); + } + + public static NamingStrategy GetContainerNamingStrategy(JsonContainerAttribute containerAttribute) + { + if (containerAttribute.NamingStrategyInstance == null) + { + if (containerAttribute.NamingStrategyType == null) + { + return null; + } + + containerAttribute.NamingStrategyInstance = CreateNamingStrategyInstance(containerAttribute.NamingStrategyType, containerAttribute.NamingStrategyParameters); + } + + return containerAttribute.NamingStrategyInstance; + } + + private static Func GetCreator(Type type) + { + Func defaultConstructor = (ReflectionUtils.HasDefaultConstructor(type, false)) + ? ReflectionDelegateFactory.CreateDefaultConstructor(type) : null; return (parameters) => @@ -201,31 +217,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); } }; } diff --git a/Src/Newtonsoft.Json/Serialization/NamingStrategy.cs b/Src/Newtonsoft.Json/Serialization/NamingStrategy.cs new file mode 100644 index 00000000..cf957af3 --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/NamingStrategy.cs @@ -0,0 +1,59 @@ +namespace Newtonsoft.Json.Serialization +{ + /// + /// A base class for resolving how property names and dictionary keys are serialized. + /// + public abstract class NamingStrategy + { + /// + /// A flag indicating whether dictionary keys should be processed. + /// Defaults to false. + /// + public bool ProcessDictionaryKeys { get; set; } + + /// + /// A flag indicating whether explicitly specified property names, + /// e.g. a property name customized with a , should be processed. + /// Defaults to false. + /// + public bool OverrideSpecifiedNames { get; set; } + + /// + /// Gets the serialized name for a given property name. + /// + /// The initial property name. + /// A flag indicating whether the property has had a name explicitly specfied. + /// The serialized property name. + public virtual string GetPropertyName(string name, bool hasSpecifiedName) + { + if (hasSpecifiedName && !OverrideSpecifiedNames) + { + return name; + } + + return ResolvePropertyName(name); + } + + /// + /// Gets the serialized key for a given dictionary key. + /// + /// The initial dictionary key. + /// The serialized dictionary key. + public virtual string GetDictionaryKey(string key) + { + if (!ProcessDictionaryKeys) + { + return key; + } + + return ResolvePropertyName(key); + } + + /// + /// Resolves the specified property name. + /// + /// The property name to resolve. + /// The resolved property name. + protected abstract string ResolvePropertyName(string name); + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/SnakeCaseNamingStrategy.cs b/Src/Newtonsoft.Json/Serialization/SnakeCaseNamingStrategy.cs new file mode 100644 index 00000000..60fd9eef --- /dev/null +++ b/Src/Newtonsoft.Json/Serialization/SnakeCaseNamingStrategy.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Serialization +{ + /// + /// A snake case naming strategy. + /// + public class SnakeCaseNamingStrategy : NamingStrategy + { + /// + /// Initializes a new instance of the class. + /// + /// + /// A flag indicating whether dictionary keys should be processed. + /// + /// + /// A flag indicating whether explicitly specified property names should be processed, + /// e.g. a property name customized with a . + /// + public SnakeCaseNamingStrategy(bool processDictionaryKeys, bool overrideSpecifiedNames) + { + ProcessDictionaryKeys = processDictionaryKeys; + OverrideSpecifiedNames = overrideSpecifiedNames; + } + + /// + /// Initializes a new instance of the class. + /// + public SnakeCaseNamingStrategy() + { + } + + /// + /// Resolves the specified property name. + /// + /// The property name to resolve. + /// The resolved property name. + protected override string ResolvePropertyName(string name) + { + return StringUtils.ToSnakeCase(name); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index e8ef53e7..887d9773 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -307,7 +307,7 @@ namespace Newtonsoft.Json.Utilities { bool[] charEscapeFlags = GetCharEscapeFlags(stringEscapeHandling, delimiter); - using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(value) ?? 16)) + using (StringWriter w = StringUtils.CreateStringWriter(value?.Length ?? 16)) { char[] buffer = null; WriteEscapedJavaScriptString(w, value, delimiter, appendDelimiters, charEscapeFlags, stringEscapeHandling, null, ref buffer); diff --git a/Src/Newtonsoft.Json/Utilities/StringUtils.cs b/Src/Newtonsoft.Json/Utilities/StringUtils.cs index 09130116..84e722e9 100644 --- a/Src/Newtonsoft.Json/Utilities/StringUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/StringUtils.cs @@ -104,16 +104,6 @@ namespace Newtonsoft.Json.Utilities return true; } - /// - /// Nulls an empty string. - /// - /// The string. - /// Null if the string was null, otherwise the string unchanged. - public static string NullEmptyString(string s) - { - return (string.IsNullOrEmpty(s)) ? null : s; - } - public static StringWriter CreateStringWriter(int capacity) { StringBuilder sb = new StringBuilder(capacity); @@ -122,18 +112,6 @@ namespace Newtonsoft.Json.Utilities return sw; } - public static int? GetLength(string value) - { - if (value == null) - { - return null; - } - else - { - return value.Length; - } - } - public static void ToCharAsUnicode(char c, char[] buffer) { buffer[0] = '\\'; @@ -190,16 +168,96 @@ namespace Newtonsoft.Json.Utilities break; } + char c; #if !(DOTNET || PORTABLE) - chars[i] = char.ToLower(chars[i], CultureInfo.InvariantCulture); + c = char.ToLower(chars[i], CultureInfo.InvariantCulture); #else - chars[i] = char.ToLowerInvariant(chars[i]); + c = char.ToLowerInvariant(chars[i]); #endif + chars[i] = c; } return new string(chars); } + internal enum SnakeCaseState + { + Start, + Lower, + Upper, + NewWord + } + + public static string ToSnakeCase(string s) + { + if (string.IsNullOrEmpty(s)) + { + return s; + } + + StringBuilder sb = new StringBuilder(); + SnakeCaseState state = SnakeCaseState.Start; + + for (int i = 0; i < s.Length; i++) + { + if (s[i] == ' ') + { + if (state != SnakeCaseState.Start) + { + state = SnakeCaseState.NewWord; + } + } + else if (char.IsUpper(s[i])) + { + switch (state) + { + case SnakeCaseState.Upper: + bool hasNext = (i + 1 < s.Length); + if (i > 0 && hasNext) + { + char nextChar = s[i + 1]; + if (!char.IsUpper(nextChar) && nextChar != '_') + { + sb.Append('_'); + } + } + break; + case SnakeCaseState.Lower: + case SnakeCaseState.NewWord: + sb.Append('_'); + break; + } + + char c; +#if !(DOTNET || PORTABLE) + c = char.ToLower(s[i], CultureInfo.InvariantCulture); +#else + c = char.ToLowerInvariant(s[i]); +#endif + sb.Append(c); + + state = SnakeCaseState.Upper; + } + else if (s[i] == '_') + { + sb.Append('_'); + state = SnakeCaseState.Start; + } + else + { + if (state == SnakeCaseState.NewWord) + { + sb.Append('_'); + } + + sb.Append(s[i]); + state = SnakeCaseState.Lower; + } + } + + return sb.ToString(); + } + public static bool IsHighSurrogate(char c) { #if !(PORTABLE40 || PORTABLE)