From d48558b53b0a516bfa15c1af50231ea1ba7c2454 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 21 Oct 2018 18:27:00 +1300 Subject: [PATCH] Add support for NamingStrategy to StringEnumConverter (#1885) --- .../Converters/RegexConverterTests.cs | 2 + .../Converters/StringEnumConverterTests.cs | 168 +++++++++++++++++- .../Utilities/EnumUtilsTests.cs | 6 +- .../Converters/StringEnumConverter.cs | 100 ++++++++++- Src/Newtonsoft.Json/JsonPropertyAttribute.cs | 4 +- .../CamelCasePropertyNamesContractResolver.cs | 44 +---- .../DefaultSerializationBinder.cs | 16 +- .../JsonSerializerInternalReader.cs | 6 +- .../JsonSerializerInternalWriter.cs | 2 +- .../Serialization/JsonTypeReflector.cs | 22 ++- Src/Newtonsoft.Json/Utilities/ConvertUtils.cs | 49 +---- Src/Newtonsoft.Json/Utilities/EnumUtils.cs | 35 ++-- .../Utilities/ReflectionUtils.cs | 36 +--- .../Utilities/StructMultiKey.cs | 61 +++++++ 14 files changed, 380 insertions(+), 171 deletions(-) create mode 100644 Src/Newtonsoft.Json/Utilities/StructMultiKey.cs diff --git a/Src/Newtonsoft.Json.Tests/Converters/RegexConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/RegexConverterTests.cs index 64c47947..e9e34dc7 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/RegexConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/RegexConverterTests.cs @@ -82,7 +82,9 @@ namespace Newtonsoft.Json.Tests.Converters string json = JsonConvert.SerializeObject(regex, Formatting.Indented, new JsonSerializerSettings { +#pragma warning disable CS0618 // Type or member is obsolete Converters = { new RegexConverter(), new StringEnumConverter() { CamelCaseText = true } }, +#pragma warning restore CS0618 // Type or member is obsolete ContractResolver = new CamelCasePropertyNamesContractResolver() }); diff --git a/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs b/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs index 42f58a13..2a6e1dc8 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/StringEnumConverterTests.cs @@ -29,6 +29,7 @@ using System.IO; using System.Runtime.Serialization; using System.Text; using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; #if DNXCORE50 using Xunit; using Test = Xunit.FactAttribute; @@ -132,39 +133,173 @@ namespace Newtonsoft.Json.Tests.Converters } [JsonConverter(typeof(StringEnumConverter), true)] - public enum CamelCaseEnum + public enum CamelCaseEnumObsolete { This, Is, CamelCase } - [JsonConverter(typeof(StringEnumConverter), true, false)] + [JsonConverter(typeof(StringEnumConverter), typeof(CamelCaseNamingStrategy))] + public enum CamelCaseEnumNew + { + This, + Is, + CamelCase + } + + [JsonConverter(typeof(StringEnumConverter), typeof(SnakeCaseNamingStrategy))] + public enum SnakeCaseEnumNew + { + This, + Is, + SnakeCase + } + + [JsonConverter(typeof(StringEnumConverter), typeof(CamelCaseNamingStrategy), new object[0], false)] public enum NotAllowIntegerValuesEnum { Foo = 0, Bar = 1 } - [JsonConverter(typeof(StringEnumConverter), true, true)] + [JsonConverter(typeof(StringEnumConverter), typeof(CamelCaseNamingStrategy))] public enum AllowIntegerValuesEnum { Foo = 0, Bar = 1 } + [JsonConverter(typeof(StringEnumConverter), typeof(CamelCaseNamingStrategy), null)] + public enum NullArgumentInAttribute + { + Foo = 0, + Bar = 1 + } + + [Test] + public void Serialize_CamelCaseFromAttribute_Obsolete() + { + string json = JsonConvert.SerializeObject(CamelCaseEnumObsolete.CamelCase); + Assert.AreEqual(@"""camelCase""", json); + } + + [Test] + public void NamingStrategyAndCamelCaseText() + { + StringEnumConverter converter = new StringEnumConverter(); + Assert.IsNull(converter.NamingStrategy); + +#pragma warning disable CS0618 // Type or member is obsolete + converter.CamelCaseText = true; +#pragma warning restore CS0618 // Type or member is obsolete + Assert.IsNotNull(converter.NamingStrategy); + Assert.AreEqual(typeof(CamelCaseNamingStrategy), converter.NamingStrategy.GetType()); + + var camelCaseInstance = converter.NamingStrategy; +#pragma warning disable CS0618 // Type or member is obsolete + converter.CamelCaseText = true; +#pragma warning restore CS0618 // Type or member is obsolete + Assert.AreEqual(camelCaseInstance, converter.NamingStrategy); + + converter.NamingStrategy = null; +#pragma warning disable CS0618 // Type or member is obsolete + Assert.IsFalse(converter.CamelCaseText); +#pragma warning restore CS0618 // Type or member is obsolete + + converter.NamingStrategy = new CamelCaseNamingStrategy(); +#pragma warning disable CS0618 // Type or member is obsolete + Assert.IsTrue(converter.CamelCaseText); +#pragma warning restore CS0618 // Type or member is obsolete + + converter.NamingStrategy = new SnakeCaseNamingStrategy(); +#pragma warning disable CS0618 // Type or member is obsolete + Assert.IsFalse(converter.CamelCaseText); +#pragma warning restore CS0618 // Type or member is obsolete + +#pragma warning disable CS0618 // Type or member is obsolete + converter.CamelCaseText = false; +#pragma warning restore CS0618 // Type or member is obsolete + Assert.IsNotNull(converter.NamingStrategy); + Assert.AreEqual(typeof(SnakeCaseNamingStrategy), converter.NamingStrategy.GetType()); + } + + [Test] + public void StringEnumConverter_CamelCaseTextCtor() + { +#pragma warning disable CS0618 // Type or member is obsolete + StringEnumConverter converter = new StringEnumConverter(true); +#pragma warning restore CS0618 // Type or member is obsolete + + Assert.IsNotNull(converter.NamingStrategy); + Assert.AreEqual(typeof(CamelCaseNamingStrategy), converter.NamingStrategy.GetType()); + Assert.AreEqual(true, converter.AllowIntegerValues); + } + + [Test] + public void StringEnumConverter_NamingStrategyTypeCtor() + { + StringEnumConverter converter = new StringEnumConverter(typeof(CamelCaseNamingStrategy), new object[] { true, true, true }, false); + + Assert.IsNotNull(converter.NamingStrategy); + Assert.AreEqual(typeof(CamelCaseNamingStrategy), converter.NamingStrategy.GetType()); + Assert.AreEqual(false, converter.AllowIntegerValues); + Assert.AreEqual(true, converter.NamingStrategy.OverrideSpecifiedNames); + Assert.AreEqual(true, converter.NamingStrategy.ProcessDictionaryKeys); + Assert.AreEqual(true, converter.NamingStrategy.ProcessExtensionDataNames); + } + + [Test] + public void StringEnumConverter_NamingStrategyTypeCtor_Null() + { + ExceptionAssert.Throws( + () => new StringEnumConverter(null), + @"Value cannot be null. +Parameter name: namingStrategyType"); + } + + [Test] + public void StringEnumConverter_NamingStrategyTypeWithArgsCtor_Null() + { + ExceptionAssert.Throws( + () => new StringEnumConverter(null, new object[] { true, true, true }, false), + @"Value cannot be null. +Parameter name: namingStrategyType"); + } + + [Test] + public void Deserialize_CamelCaseFromAttribute_Obsolete() + { + CamelCaseEnumObsolete e = JsonConvert.DeserializeObject(@"""camelCase"""); + Assert.AreEqual(CamelCaseEnumObsolete.CamelCase, e); + } + [Test] public void Serialize_CamelCaseFromAttribute() { - string json = JsonConvert.SerializeObject(CamelCaseEnum.CamelCase); + string json = JsonConvert.SerializeObject(CamelCaseEnumNew.CamelCase); Assert.AreEqual(@"""camelCase""", json); } [Test] public void Deserialize_CamelCaseFromAttribute() { - CamelCaseEnum e = JsonConvert.DeserializeObject(@"""camelCase"""); - Assert.AreEqual(CamelCaseEnum.CamelCase, e); + CamelCaseEnumNew e = JsonConvert.DeserializeObject(@"""camelCase"""); + Assert.AreEqual(CamelCaseEnumNew.CamelCase, e); + } + + [Test] + public void Serialize_SnakeCaseFromAttribute() + { + string json = JsonConvert.SerializeObject(SnakeCaseEnumNew.SnakeCase); + Assert.AreEqual(@"""snake_case""", json); + } + + [Test] + public void Deserialize_SnakeCaseFromAttribute() + { + SnakeCaseEnumNew e = JsonConvert.DeserializeObject(@"""snake_case"""); + Assert.AreEqual(SnakeCaseEnumNew.SnakeCase, e); } [Test] @@ -176,6 +311,17 @@ namespace Newtonsoft.Json.Tests.Converters }); } + [Test] + public void CannotPassNullArgumentToConverter() + { + var ex = ExceptionAssert.Throws(() => + { + JsonConvert.DeserializeObject(@"""9"""); + }); + + Assert.AreEqual("Cannot pass a null parameter to the constructor.", ex.InnerException.Message); + } + [Test] public void Deserialize_AllowIntegerValuesAttribute() { @@ -311,7 +457,9 @@ namespace Newtonsoft.Json.Tests.Converters NullableStoreColor2 = null }; +#pragma warning disable CS0618 // Type or member is obsolete string json = JsonConvert.SerializeObject(enumClass, Formatting.Indented, new StringEnumConverter { CamelCaseText = true }); +#pragma warning restore CS0618 // Type or member is obsolete StringAssert.AreEqual(@"{ ""StoreColor"": ""red"", @@ -474,7 +622,9 @@ namespace Newtonsoft.Json.Tests.Converters Enum = FlagsTestEnum.First | FlagsTestEnum.Second }; +#pragma warning disable CS0618 // Type or member is obsolete string json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter { CamelCaseText = true }); +#pragma warning restore CS0618 // Type or member is obsolete StringAssert.AreEqual(@"{ ""Enum"": ""first, second"" }", json); @@ -487,7 +637,9 @@ namespace Newtonsoft.Json.Tests.Converters ""Enum"": ""first, second"" }"; +#pragma warning disable CS0618 // Type or member is obsolete EnumContainer c = JsonConvert.DeserializeObject>(json, new StringEnumConverter { CamelCaseText = true }); +#pragma warning restore CS0618 // Type or member is obsolete Assert.AreEqual(FlagsTestEnum.First | FlagsTestEnum.Second, c.Enum); } @@ -565,7 +717,9 @@ namespace Newtonsoft.Json.Tests.Converters (Foo)int.MaxValue }; +#pragma warning disable CS0618 // Type or member is obsolete string json1 = JsonConvert.SerializeObject(lfoo, Formatting.Indented, new StringEnumConverter { CamelCaseText = true }); +#pragma warning restore CS0618 // Type or member is obsolete StringAssert.AreEqual(@"[ ""Bat, baz"", @@ -588,7 +742,9 @@ namespace Newtonsoft.Json.Tests.Converters List lbar = new List() { Bar.FooBar, Bar.Bat, Bar.SerializeAsBaz }; +#pragma warning disable CS0618 // Type or member is obsolete string json2 = JsonConvert.SerializeObject(lbar, Formatting.Indented, new StringEnumConverter { CamelCaseText = true }); +#pragma warning restore CS0618 // Type or member is obsolete StringAssert.AreEqual(@"[ ""foo_bar"", diff --git a/Src/Newtonsoft.Json.Tests/Utilities/EnumUtilsTests.cs b/Src/Newtonsoft.Json.Tests/Utilities/EnumUtilsTests.cs index a9c7518d..9abf0d40 100644 --- a/Src/Newtonsoft.Json.Tests/Utilities/EnumUtilsTests.cs +++ b/Src/Newtonsoft.Json.Tests/Utilities/EnumUtilsTests.cs @@ -51,7 +51,7 @@ namespace Newtonsoft.Json.Tests.Utilities { Type enumType = expected.GetType(); - Enum result = (Enum)EnumUtils.ParseEnum(enumType, value, false); + Enum result = (Enum)EnumUtils.ParseEnum(enumType, null, value, false); Assert.AreEqual(expected, result); } @@ -63,7 +63,7 @@ namespace Newtonsoft.Json.Tests.Utilities { try { - EnumUtils.ParseEnum(enumType, value, false); + EnumUtils.ParseEnum(enumType, null, value, false); } catch (Exception ex) when (ex.GetType() == exceptionType) { @@ -80,7 +80,7 @@ namespace Newtonsoft.Json.Tests.Utilities [TestCaseSource(nameof(ToString_Format_TestData))] public static void ToString_Format(Enum e, string expected) { - EnumUtils.TryToString(e.GetType(), e, false, out string result); + EnumUtils.TryToString(e.GetType(), e, null, out string result); Assert.AreEqual(expected, result); } diff --git a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs index 03f54052..6639235e 100644 --- a/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs +++ b/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs @@ -31,6 +31,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.Serialization; using Newtonsoft.Json.Utilities; +using Newtonsoft.Json.Serialization; #if !HAVE_LINQ using Newtonsoft.Json.Utilities.LinqBridge; #else @@ -50,40 +51,121 @@ namespace Newtonsoft.Json.Converters /// The default value is false. /// /// true if the written enum text will be camel case; otherwise, false. - public bool CamelCaseText { get; set; } + [Obsolete("StringEnumConverter.CamelCaseText is obsolete. Set StringEnumConverter.NamingStrategy with CamelCaseNamingStrategy instead.")] + public bool CamelCaseText + { + get => NamingStrategy is CamelCaseNamingStrategy ? true : false; + set + { + if (value) + { + if (NamingStrategy is CamelCaseNamingStrategy) + { + return; + } + + NamingStrategy = new CamelCaseNamingStrategy(); + } + else + { + if (!(NamingStrategy is CamelCaseNamingStrategy)) + { + return; + } + + NamingStrategy = null; + } + } + } + + /// + /// Gets or sets the naming strategy used to resolve how enum text is written. + /// + /// The naming strategy used to resolve how enum text is written. + public NamingStrategy NamingStrategy { get; set; } /// /// Gets or sets a value indicating whether integer values are allowed when serializing and deserializing. /// The default value is true. /// /// true if integers are allowed when serializing and deserializing; otherwise, false. - public bool AllowIntegerValues { get; set; } + public bool AllowIntegerValues { get; set; } = true; /// /// Initializes a new instance of the class. /// public StringEnumConverter() { - AllowIntegerValues = true; } /// /// Initializes a new instance of the class. /// /// true if the written enum text will be camel case; otherwise, false. + [Obsolete("StringEnumConverter(bool) is obsolete. Create a converter with StringEnumConverter(NamingStrategy, bool) instead.")] public StringEnumConverter(bool camelCaseText) - : this() { - CamelCaseText = camelCaseText; + if (camelCaseText) + { + NamingStrategy = new CamelCaseNamingStrategy(); + } } /// /// Initializes a new instance of the class. /// - /// true if the written enum text will be camel case; otherwise, false. + /// The naming strategy used to resolve how enum text is written. /// true if integers are allowed when serializing and deserializing; otherwise, false. - public StringEnumConverter(bool camelCaseText, bool allowIntegerValues) : this(camelCaseText) + public StringEnumConverter(NamingStrategy namingStrategy, bool allowIntegerValues = true) { + NamingStrategy = namingStrategy; + AllowIntegerValues = allowIntegerValues; + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the used to write enum text. + public StringEnumConverter(Type namingStrategyType) + { + ValidationUtils.ArgumentNotNull(namingStrategyType, nameof(namingStrategyType)); + + NamingStrategy = JsonTypeReflector.CreateNamingStrategyInstance(namingStrategyType, null); + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the used to write enum text. + /// + /// The parameter list to use when constructing the described by . + /// 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. + /// + public StringEnumConverter(Type namingStrategyType, object[] namingStrategyParameters) + { + ValidationUtils.ArgumentNotNull(namingStrategyType, nameof(namingStrategyType)); + + NamingStrategy = JsonTypeReflector.CreateNamingStrategyInstance(namingStrategyType, namingStrategyParameters); + } + + /// + /// Initializes a new instance of the class. + /// + /// The of the used to write enum text. + /// + /// The parameter list to use when constructing the described by . + /// 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. + /// + /// true if integers are allowed when serializing and deserializing; otherwise, false. + public StringEnumConverter(Type namingStrategyType, object[] namingStrategyParameters, bool allowIntegerValues) + { + ValidationUtils.ArgumentNotNull(namingStrategyType, nameof(namingStrategyType)); + + NamingStrategy = JsonTypeReflector.CreateNamingStrategyInstance(namingStrategyType, namingStrategyParameters); AllowIntegerValues = allowIntegerValues; } @@ -103,7 +185,7 @@ namespace Newtonsoft.Json.Converters Enum e = (Enum)value; - if (!EnumUtils.TryToString(e.GetType(), value, CamelCaseText, out string enumName)) + if (!EnumUtils.TryToString(e.GetType(), value, NamingStrategy, out string enumName)) { if (!AllowIntegerValues) { @@ -153,7 +235,7 @@ namespace Newtonsoft.Json.Converters return null; } - return EnumUtils.ParseEnum(t, enumText, !AllowIntegerValues); + return EnumUtils.ParseEnum(t, NamingStrategy, enumText, !AllowIntegerValues); } if (reader.TokenType == JsonToken.Integer) diff --git a/Src/Newtonsoft.Json/JsonPropertyAttribute.cs b/Src/Newtonsoft.Json/JsonPropertyAttribute.cs index 979a43b3..ec6532fa 100644 --- a/Src/Newtonsoft.Json/JsonPropertyAttribute.cs +++ b/Src/Newtonsoft.Json/JsonPropertyAttribute.cs @@ -49,9 +49,9 @@ namespace Newtonsoft.Json internal TypeNameHandling? _itemTypeNameHandling; /// - /// Gets or sets the used when serializing the property's collection items. + /// Gets or sets the type used when serializing the property's collection items. /// - /// The collection's items . + /// The collection's items type. public Type ItemConverterType { get; set; } /// diff --git a/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs b/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs index 8f453621..c0107cfd 100644 --- a/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/CamelCasePropertyNamesContractResolver.cs @@ -30,38 +30,6 @@ using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Serialization { - internal readonly struct ResolverContractKey : IEquatable - { - private readonly Type _resolverType; - private readonly Type _contractType; - - public ResolverContractKey(Type resolverType, Type contractType) - { - _resolverType = resolverType; - _contractType = contractType; - } - - public override int GetHashCode() - { - return _resolverType.GetHashCode() ^ _contractType.GetHashCode(); - } - - public override bool Equals(object obj) - { - if (!(obj is ResolverContractKey key)) - { - return false; - } - - return Equals(key); - } - - public bool Equals(ResolverContractKey other) - { - return (_resolverType == other._resolverType && _contractType == other._contractType); - } - } - /// /// Resolves member mappings for a type, camel casing property names. /// @@ -69,7 +37,7 @@ namespace Newtonsoft.Json.Serialization { private static readonly object TypeContractCacheLock = new object(); private static readonly DefaultJsonNameTable NameTable = new DefaultJsonNameTable(); - private static Dictionary _contractCache; + private static Dictionary, JsonContract> _contractCache; /// /// Initializes a new instance of the class. @@ -96,8 +64,8 @@ namespace Newtonsoft.Json.Serialization } // for backwards compadibility the CamelCasePropertyNamesContractResolver shares contracts between instances - ResolverContractKey key = new ResolverContractKey(GetType(), type); - Dictionary cache = _contractCache; + StructMultiKey key = new StructMultiKey(GetType(), type); + Dictionary, JsonContract> cache = _contractCache; if (cache == null || !cache.TryGetValue(key, out JsonContract contract)) { contract = CreateContract(type); @@ -106,9 +74,9 @@ namespace Newtonsoft.Json.Serialization lock (TypeContractCacheLock) { cache = _contractCache; - Dictionary updatedCache = (cache != null) - ? new Dictionary(cache) - : new Dictionary(); + Dictionary, JsonContract> updatedCache = (cache != null) + ? new Dictionary, JsonContract>(cache) + : new Dictionary, JsonContract>(); updatedCache[key] = contract; _contractCache = updatedCache; diff --git a/Src/Newtonsoft.Json/Serialization/DefaultSerializationBinder.cs b/Src/Newtonsoft.Json/Serialization/DefaultSerializationBinder.cs index b43e4b0b..90af8033 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultSerializationBinder.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultSerializationBinder.cs @@ -43,20 +43,20 @@ namespace Newtonsoft.Json.Serialization { internal static readonly DefaultSerializationBinder Instance = new DefaultSerializationBinder(); - private readonly ThreadSafeStore _typeCache; + private readonly ThreadSafeStore, Type> _typeCache; /// /// Initializes a new instance of the class. /// public DefaultSerializationBinder() { - _typeCache = new ThreadSafeStore(GetTypeFromTypeNameKey); + _typeCache = new ThreadSafeStore, Type>(GetTypeFromTypeNameKey); } - private Type GetTypeFromTypeNameKey(TypeNameKey typeNameKey) + private Type GetTypeFromTypeNameKey(StructMultiKey typeNameKey) { - string assemblyName = typeNameKey.AssemblyName; - string typeName = typeNameKey.TypeName; + string assemblyName = typeNameKey.Value1; + string typeName = typeNameKey.Value2; if (assemblyName != null) { @@ -159,7 +159,7 @@ namespace Newtonsoft.Json.Serialization { string typeArgAssemblyQualifiedName = typeName.Substring(typeArgStartIndex, i - typeArgStartIndex); - TypeNameKey typeNameKey = ReflectionUtils.SplitFullyQualifiedTypeName(typeArgAssemblyQualifiedName); + StructMultiKey typeNameKey = ReflectionUtils.SplitFullyQualifiedTypeName(typeArgAssemblyQualifiedName); genericTypeArguments.Add(GetTypeByName(typeNameKey)); } break; @@ -173,7 +173,7 @@ namespace Newtonsoft.Json.Serialization return type; } - private Type GetTypeByName(TypeNameKey typeNameKey) + private Type GetTypeByName(StructMultiKey typeNameKey) { return _typeCache.Get(typeNameKey); } @@ -188,7 +188,7 @@ namespace Newtonsoft.Json.Serialization /// public override Type BindToType(string assemblyName, string typeName) { - return GetTypeByName(new TypeNameKey(assemblyName, typeName)); + return GetTypeByName(new StructMultiKey(assemblyName, typeName)); } /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 38993e78..d286f7b0 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -777,12 +777,12 @@ namespace Newtonsoft.Json.Serialization if (resolvedTypeNameHandling != TypeNameHandling.None) { - TypeNameKey typeNameKey = ReflectionUtils.SplitFullyQualifiedTypeName(qualifiedTypeName); + StructMultiKey typeNameKey = ReflectionUtils.SplitFullyQualifiedTypeName(qualifiedTypeName); Type specifiedType; try { - specifiedType = Serializer._serializationBinder.BindToType(typeNameKey.AssemblyName, typeNameKey.TypeName); + specifiedType = Serializer._serializationBinder.BindToType(typeNameKey.Value1, typeNameKey.Value2); } catch (Exception ex) { @@ -955,7 +955,7 @@ namespace Newtonsoft.Json.Serialization { if (value is string s) { - return EnumUtils.ParseEnum(contract.NonNullableUnderlyingType, s, false); + return EnumUtils.ParseEnum(contract.NonNullableUnderlyingType, null, s, false); } if (ConvertUtils.IsInteger(primitiveContract.TypeCode)) { diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 890daf9e..9ce79c69 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -1150,7 +1150,7 @@ namespace Newtonsoft.Json.Serialization { escape = true; - if (primitiveContract.IsEnum && EnumUtils.TryToString(primitiveContract.NonNullableUnderlyingType, name, false, out string enumName)) + if (primitiveContract.IsEnum && EnumUtils.TryToString(primitiveContract.NonNullableUnderlyingType, name, null, out string enumName)) { return enumName; } diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index 9025c94b..25100201 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -198,18 +198,18 @@ namespace Newtonsoft.Json.Serialization /// Lookup and create an instance of the type described by the argument. /// /// The type to create. - /// Optional arguments to pass to an initializing constructor of the JsonConverter. + /// Optional arguments to pass to an initializing constructor of the JsonConverter. /// If null, the default constructor is used. - public static JsonConverter CreateJsonConverterInstance(Type converterType, object[] converterArgs) + public static JsonConverter CreateJsonConverterInstance(Type converterType, object[] args) { Func converterCreator = CreatorCache.Get(converterType); - return (JsonConverter)converterCreator(converterArgs); + return (JsonConverter)converterCreator(args); } - public static NamingStrategy CreateNamingStrategyInstance(Type namingStrategyType, object[] converterArgs) + public static NamingStrategy CreateNamingStrategyInstance(Type namingStrategyType, object[] args) { Func converterCreator = CreatorCache.Get(namingStrategyType); - return (NamingStrategy)converterCreator(converterArgs); + return (NamingStrategy)converterCreator(args); } public static NamingStrategy GetContainerNamingStrategy(JsonContainerAttribute containerAttribute) @@ -239,10 +239,18 @@ namespace Newtonsoft.Json.Serialization { if (parameters != null) { - Type[] paramTypes = parameters.Select(param => param.GetType()).ToArray(); + Type[] paramTypes = parameters.Select(param => + { + if (param == null) + { + throw new InvalidOperationException("Cannot pass a null parameter to the constructor."); + } + + return param.GetType(); + }).ToArray(); ConstructorInfo parameterizedConstructorInfo = type.GetConstructor(paramTypes); - if (null != parameterizedConstructorInfo) + if (parameterizedConstructorInfo != null) { ObjectConstructor parameterizedConstructor = ReflectionDelegateFactory.CreateParameterizedConstructor(parameterizedConstructorInfo); return parameterizedConstructor(parameters); diff --git a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs index 0e3f885c..f00d8947 100644 --- a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs @@ -248,46 +248,15 @@ namespace Newtonsoft.Json.Utilities #endif } - internal readonly struct TypeConvertKey : IEquatable + private static readonly ThreadSafeStore, Func> CastConverters = + new ThreadSafeStore, Func>(CreateCastConverter); + + private static Func CreateCastConverter(StructMultiKey t) { - public Type InitialType { get; } - - public Type TargetType { get; } - - public TypeConvertKey(Type initialType, Type targetType) - { - InitialType = initialType; - TargetType = targetType; - } - - public override int GetHashCode() - { - return InitialType.GetHashCode() ^ TargetType.GetHashCode(); - } - - public override bool Equals(object obj) - { - if (!(obj is TypeConvertKey key)) - { - return false; - } - - return Equals(key); - } - - public bool Equals(TypeConvertKey other) - { - return (InitialType == other.InitialType && TargetType == other.TargetType); - } - } - - private static readonly ThreadSafeStore> CastConverters = - new ThreadSafeStore>(CreateCastConverter); - - private static Func CreateCastConverter(TypeConvertKey t) - { - MethodInfo castMethodInfo = t.TargetType.GetMethod("op_Implicit", new[] { t.InitialType }) - ?? t.TargetType.GetMethod("op_Explicit", new[] { t.InitialType }); + Type initialType = t.Value1; + Type targetType = t.Value2; + MethodInfo castMethodInfo = targetType.GetMethod("op_Implicit", new[] { initialType }) + ?? targetType.GetMethod("op_Explicit", new[] { initialType }); if (castMethodInfo == null) { @@ -630,7 +599,7 @@ namespace Newtonsoft.Json.Utilities return value; } - Func castConverter = CastConverters.Get(new TypeConvertKey(valueType, targetType)); + Func castConverter = CastConverters.Get(new StructMultiKey(valueType, targetType)); if (castConverter != null) { return castConverter(value); diff --git a/Src/Newtonsoft.Json/Utilities/EnumUtils.cs b/Src/Newtonsoft.Json/Utilities/EnumUtils.cs index d9f4c4da..0a782f60 100644 --- a/Src/Newtonsoft.Json/Utilities/EnumUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/EnumUtils.cs @@ -35,6 +35,7 @@ using System.Linq; #endif using System.Reflection; using System.Text; +using Newtonsoft.Json.Serialization; namespace Newtonsoft.Json.Utilities { @@ -43,10 +44,11 @@ namespace Newtonsoft.Json.Utilities private const char EnumSeparatorChar = ','; private const string EnumSeparatorString = ", "; - private static readonly ThreadSafeStore ValuesAndNamesPerEnum = new ThreadSafeStore(InitializeValuesAndNames); + private static readonly ThreadSafeStore, EnumInfo> ValuesAndNamesPerEnum = new ThreadSafeStore, EnumInfo>(InitializeValuesAndNames); - private static EnumInfo InitializeValuesAndNames(Type enumType) + private static EnumInfo InitializeValuesAndNames(StructMultiKey key) { + Type enumType = key.Value1; string[] names = Enum.GetNames(enumType); string[] resolvedNames = new string[names.Length]; ulong[] values = new ulong[names.Length]; @@ -72,7 +74,9 @@ namespace Newtonsoft.Json.Utilities resolvedName = name; #endif - resolvedNames[i] = resolvedName; + resolvedNames[i] = key.Value2 != null + ? key.Value2.GetPropertyName(resolvedName, false) + : resolvedName; } bool isFlags = enumType.IsDefined(typeof(FlagsAttribute), false); @@ -113,9 +117,9 @@ namespace Newtonsoft.Json.Utilities return selectedFlagsValues; } - public static bool TryToString(Type enumType, object value, bool camelCaseText, out string name) + public static bool TryToString(Type enumType, object value, NamingStrategy namingStrategy, out string name) { - EnumInfo enumInfo = ValuesAndNamesPerEnum.Get(enumType); + EnumInfo enumInfo = ValuesAndNamesPerEnum.Get(new StructMultiKey(enumType, namingStrategy)); ulong v = ToUInt64(value); if (!enumInfo.IsFlags) @@ -124,11 +128,6 @@ namespace Newtonsoft.Json.Utilities if (index >= 0) { name = enumInfo.ResolvedNames[index]; - if (camelCaseText) - { - name = StringUtils.ToCamelCase(name); - } - return true; } @@ -138,12 +137,12 @@ namespace Newtonsoft.Json.Utilities } else // These are flags OR'ed together (We treat everything as unsigned types) { - name = InternalFlagsFormat(enumInfo, v, camelCaseText); + name = InternalFlagsFormat(enumInfo, v); return name != null; } } - private static String InternalFlagsFormat(EnumInfo entry, ulong result, bool camelCaseText) + private static string InternalFlagsFormat(EnumInfo entry, ulong result) { string[] resolvedNames = entry.ResolvedNames; ulong[] values = entry.Values; @@ -172,7 +171,7 @@ namespace Newtonsoft.Json.Utilities } string resolvedName = resolvedNames[index]; - sb.Insert(0, camelCaseText ? StringUtils.ToCamelCase(resolvedName) : resolvedName); + sb.Insert(0, resolvedName); firstTime = false; } @@ -191,10 +190,6 @@ namespace Newtonsoft.Json.Utilities if (values.Length > 0 && values[0] == 0) { returnString = resolvedNames[0]; // Zero was one of the enum values. - if (camelCaseText) - { - returnString = StringUtils.ToCamelCase(returnString); - } } else { @@ -211,7 +206,7 @@ namespace Newtonsoft.Json.Utilities public static EnumInfo GetEnumValuesAndNames(Type enumType) { - return ValuesAndNamesPerEnum.Get(enumType); + return ValuesAndNamesPerEnum.Get(new StructMultiKey(enumType, null)); } private static ulong ToUInt64(object value) @@ -247,7 +242,7 @@ namespace Newtonsoft.Json.Utilities } } - public static object ParseEnum(Type enumType, string value, bool disallowNumber) + public static object ParseEnum(Type enumType, NamingStrategy namingStrategy, string value, bool disallowNumber) { ValidationUtils.ArgumentNotNull(enumType, nameof(enumType)); ValidationUtils.ArgumentNotNull(value, nameof(value)); @@ -257,7 +252,7 @@ namespace Newtonsoft.Json.Utilities throw new ArgumentException("Type provided must be an Enum.", nameof(enumType)); } - EnumInfo entry = ValuesAndNamesPerEnum.Get(enumType); + EnumInfo entry = ValuesAndNamesPerEnum.Get(new StructMultiKey(enumType, namingStrategy)); string[] enumNames = entry.Names; string[] resolvedNames = entry.ResolvedNames; ulong[] enumValues = entry.Values; diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index 5971ead3..056d55e5 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -824,7 +824,7 @@ namespace Newtonsoft.Json.Utilities } #endif - public static TypeNameKey SplitFullyQualifiedTypeName(string fullyQualifiedTypeName) + public static StructMultiKey SplitFullyQualifiedTypeName(string fullyQualifiedTypeName) { int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName); @@ -842,7 +842,7 @@ namespace Newtonsoft.Json.Utilities assemblyName = null; } - return new TypeNameKey(assemblyName, typeName); + return new StructMultiKey(assemblyName, typeName); } private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName) @@ -1094,36 +1094,4 @@ namespace Newtonsoft.Json.Utilities return Activator.CreateInstance(type); } } - - internal readonly struct TypeNameKey : IEquatable - { - internal readonly string AssemblyName; - internal readonly string TypeName; - - public TypeNameKey(string assemblyName, string typeName) - { - AssemblyName = assemblyName; - TypeName = typeName; - } - - public override int GetHashCode() - { - return (AssemblyName?.GetHashCode() ?? 0) ^ (TypeName?.GetHashCode() ?? 0); - } - - public override bool Equals(object obj) - { - if (!(obj is TypeNameKey key)) - { - return false; - } - - return Equals(key); - } - - public bool Equals(TypeNameKey other) - { - return (AssemblyName == other.AssemblyName && TypeName == other.TypeName); - } - } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/StructMultiKey.cs b/Src/Newtonsoft.Json/Utilities/StructMultiKey.cs new file mode 100644 index 00000000..10428aaa --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/StructMultiKey.cs @@ -0,0 +1,61 @@ +#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; + +namespace Newtonsoft.Json.Utilities +{ + internal readonly struct StructMultiKey : IEquatable> + { + public readonly T1 Value1; + public readonly T2 Value2; + + public StructMultiKey(T1 v1, T2 v2) + { + Value1 = v1; + Value2 = v2; + } + + public override int GetHashCode() + { + return (Value1?.GetHashCode() ?? 0) ^ (Value2?.GetHashCode() ?? 0); + } + + public override bool Equals(object obj) + { + if (!(obj is StructMultiKey key)) + { + return false; + } + + return Equals(key); + } + + public bool Equals(StructMultiKey other) + { + return (Equals(Value1, other.Value1) && Equals(Value2, other.Value2)); + } + } +} \ No newline at end of file