#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 System.Globalization; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Text.RegularExpressions; using Newtonsoft.Json.Converters; #if DNXCORE50 using Xunit; using Test = Xunit.FactAttribute; using Assert = Newtonsoft.Json.Tests.XUnitAssert; #else using NUnit.Framework; #endif using Newtonsoft.Json.Tests.TestObjects; namespace Newtonsoft.Json.Tests.Converters { [TestFixture] public class StringEnumConverterTests : TestFixtureBase { public class EnumClass { public StoreColor StoreColor { get; set; } public StoreColor? NullableStoreColor1 { get; set; } public StoreColor? NullableStoreColor2 { get; set; } } public class EnumContainer { public T Enum { get; set; } } [Flags] public enum FlagsTestEnum { Default = 0, First = 1, Second = 2 } public enum NegativeEnum { Negative = -1, Zero = 0, Positive = 1 } #if !NET20 public enum NamedEnum { [EnumMember(Value = "@first")] First, [EnumMember(Value = "@second")] Second, Third } public enum NamedEnumDuplicate { [EnumMember(Value = "Third")] First, [EnumMember(Value = "@second")] Second, Third } public enum NamedEnumWithComma { [EnumMember(Value = "@first")] First, [EnumMember(Value = "@second")] Second, [EnumMember(Value = ",third")] Third, [EnumMember(Value = ",")] JustComma } #endif public class NegativeEnumClass { public NegativeEnum Value1 { get; set; } public NegativeEnum Value2 { get; set; } } [JsonConverter(typeof(StringEnumConverter), true)] public enum CamelCaseEnum { This, Is, CamelCase } [Test] public void Serialize_CamelCaseFromAttribute() { string json = JsonConvert.SerializeObject(CamelCaseEnum.CamelCase); Assert.AreEqual(@"""camelCase""", json); } [Test] public void Deserialize_CamelCaseFromAttribute() { CamelCaseEnum e = JsonConvert.DeserializeObject(@"""camelCase"""); Assert.AreEqual(CamelCaseEnum.CamelCase, e); } #if !NET20 [Test] public void NamedEnumDuplicateTest() { ExceptionAssert.Throws(() => { EnumContainer c = new EnumContainer { Enum = NamedEnumDuplicate.First }; JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter()); }, "Enum name 'Third' already exists on enum 'NamedEnumDuplicate'."); } [Test] public void SerializeNameEnumTest() { EnumContainer c = new EnumContainer { Enum = NamedEnum.First }; string json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter()); StringAssert.AreEqual(@"{ ""Enum"": ""@first"" }", json); c = new EnumContainer { Enum = NamedEnum.Third }; json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter()); StringAssert.AreEqual(@"{ ""Enum"": ""Third"" }", json); } [Test] public void NamedEnumCommaTest() { EnumContainer c = new EnumContainer { Enum = NamedEnumWithComma.Third }; string json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter()); Assert.AreEqual(@"{ ""Enum"": "",third"" }", json); EnumContainer c2 = JsonConvert.DeserializeObject>(json, new StringEnumConverter()); Assert.AreEqual(NamedEnumWithComma.Third, c2.Enum); } [Test] public void NamedEnumCommaTest2() { EnumContainer c = new EnumContainer { Enum = NamedEnumWithComma.JustComma }; string json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter()); Assert.AreEqual(@"{ ""Enum"": "","" }", json); EnumContainer c2 = JsonConvert.DeserializeObject>(json, new StringEnumConverter()); Assert.AreEqual(NamedEnumWithComma.JustComma, c2.Enum); } [Test] public void DeserializeNameEnumTest() { string json = @"{ ""Enum"": ""@first"" }"; EnumContainer c = JsonConvert.DeserializeObject>(json, new StringEnumConverter()); Assert.AreEqual(NamedEnum.First, c.Enum); json = @"{ ""Enum"": ""Third"" }"; c = JsonConvert.DeserializeObject>(json, new StringEnumConverter()); Assert.AreEqual(NamedEnum.Third, c.Enum); } #endif [Test] public void SerializeEnumClass() { EnumClass enumClass = new EnumClass(); enumClass.StoreColor = StoreColor.Red; enumClass.NullableStoreColor1 = StoreColor.White; enumClass.NullableStoreColor2 = null; string json = JsonConvert.SerializeObject(enumClass, Formatting.Indented, new StringEnumConverter()); StringAssert.AreEqual(@"{ ""StoreColor"": ""Red"", ""NullableStoreColor1"": ""White"", ""NullableStoreColor2"": null }", json); } [Test] public void SerializeEnumClassWithCamelCase() { EnumClass enumClass = new EnumClass(); enumClass.StoreColor = StoreColor.Red; enumClass.NullableStoreColor1 = StoreColor.DarkGoldenrod; enumClass.NullableStoreColor2 = null; string json = JsonConvert.SerializeObject(enumClass, Formatting.Indented, new StringEnumConverter { CamelCaseText = true }); StringAssert.AreEqual(@"{ ""StoreColor"": ""red"", ""NullableStoreColor1"": ""darkGoldenrod"", ""NullableStoreColor2"": null }", json); } [Test] public void SerializeEnumClassUndefined() { EnumClass enumClass = new EnumClass(); enumClass.StoreColor = (StoreColor)1000; enumClass.NullableStoreColor1 = (StoreColor)1000; enumClass.NullableStoreColor2 = null; string json = JsonConvert.SerializeObject(enumClass, Formatting.Indented, new StringEnumConverter()); StringAssert.AreEqual(@"{ ""StoreColor"": 1000, ""NullableStoreColor1"": 1000, ""NullableStoreColor2"": null }", json); } [Test] public void SerializeFlagEnum() { EnumClass enumClass = new EnumClass(); enumClass.StoreColor = StoreColor.Red | StoreColor.White; enumClass.NullableStoreColor1 = StoreColor.White & StoreColor.Yellow; enumClass.NullableStoreColor2 = StoreColor.Red | StoreColor.White | StoreColor.Black; string json = JsonConvert.SerializeObject(enumClass, Formatting.Indented, new StringEnumConverter()); StringAssert.AreEqual(@"{ ""StoreColor"": ""Red, White"", ""NullableStoreColor1"": 0, ""NullableStoreColor2"": ""Black, Red, White"" }", json); } [Test] public void SerializeNegativeEnum() { NegativeEnumClass negativeEnumClass = new NegativeEnumClass(); negativeEnumClass.Value1 = NegativeEnum.Negative; negativeEnumClass.Value2 = (NegativeEnum)int.MinValue; string json = JsonConvert.SerializeObject(negativeEnumClass, Formatting.Indented, new StringEnumConverter()); StringAssert.AreEqual(@"{ ""Value1"": ""Negative"", ""Value2"": -2147483648 }", json); } [Test] public void DeserializeNegativeEnum() { string json = @"{ ""Value1"": ""Negative"", ""Value2"": -2147483648 }"; NegativeEnumClass negativeEnumClass = JsonConvert.DeserializeObject(json, new StringEnumConverter()); Assert.AreEqual(NegativeEnum.Negative, negativeEnumClass.Value1); Assert.AreEqual((NegativeEnum)int.MinValue, negativeEnumClass.Value2); } [Test] public void DeserializeFlagEnum() { string json = @"{ ""StoreColor"": ""Red, White"", ""NullableStoreColor1"": 0, ""NullableStoreColor2"": ""black, Red, White"" }"; EnumClass enumClass = JsonConvert.DeserializeObject(json, new StringEnumConverter()); Assert.AreEqual(StoreColor.Red | StoreColor.White, enumClass.StoreColor); Assert.AreEqual((StoreColor)0, enumClass.NullableStoreColor1); Assert.AreEqual(StoreColor.Red | StoreColor.White | StoreColor.Black, enumClass.NullableStoreColor2); } [Test] public void DeserializeEnumClass() { string json = @"{ ""StoreColor"": ""Red"", ""NullableStoreColor1"": ""White"", ""NullableStoreColor2"": null }"; EnumClass enumClass = JsonConvert.DeserializeObject(json, new StringEnumConverter()); Assert.AreEqual(StoreColor.Red, enumClass.StoreColor); Assert.AreEqual(StoreColor.White, enumClass.NullableStoreColor1); Assert.AreEqual(null, enumClass.NullableStoreColor2); } [Test] public void DeserializeEnumClassUndefined() { string json = @"{ ""StoreColor"": 1000, ""NullableStoreColor1"": 1000, ""NullableStoreColor2"": null }"; EnumClass enumClass = JsonConvert.DeserializeObject(json, new StringEnumConverter()); Assert.AreEqual((StoreColor)1000, enumClass.StoreColor); Assert.AreEqual((StoreColor)1000, enumClass.NullableStoreColor1); Assert.AreEqual(null, enumClass.NullableStoreColor2); } [Test] public void CamelCaseTextFlagEnumSerialization() { EnumContainer c = new EnumContainer { Enum = FlagsTestEnum.First | FlagsTestEnum.Second }; string json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter { CamelCaseText = true }); StringAssert.AreEqual(@"{ ""Enum"": ""first, second"" }", json); } [Test] public void CamelCaseTextFlagEnumDeserialization() { string json = @"{ ""Enum"": ""first, second"" }"; EnumContainer c = JsonConvert.DeserializeObject>(json, new StringEnumConverter { CamelCaseText = true }); Assert.AreEqual(FlagsTestEnum.First | FlagsTestEnum.Second, c.Enum); } [Test] public void DeserializeEmptyStringIntoNullable() { string json = @"{ ""StoreColor"": ""Red"", ""NullableStoreColor1"": ""White"", ""NullableStoreColor2"": """" }"; EnumClass c = JsonConvert.DeserializeObject(json, new StringEnumConverter()); Assert.IsNull(c.NullableStoreColor2); } [Test] public void DeserializeInvalidString() { string json = "{ \"Value\" : \"Three\" }"; ExceptionAssert.Throws(() => { var serializer = new JsonSerializer(); serializer.Converters.Add(new StringEnumConverter()); serializer.Deserialize(new JsonTextReader(new StringReader(json))); }, @"Error converting value ""Three"" to type 'Newtonsoft.Json.Tests.Converters.StringEnumConverterTests+MyEnum'. Path 'Value', line 1, position 19."); } public class Bucket { public MyEnum Value; } public enum MyEnum { Alpha, Beta, } [Test] public void DeserializeIntegerButNotAllowed() { string json = "{ \"Value\" : 123 }"; try { var serializer = new JsonSerializer(); serializer.Converters.Add(new StringEnumConverter { AllowIntegerValues = false }); serializer.Deserialize(new JsonTextReader(new StringReader(json))); } catch (JsonSerializationException ex) { Assert.AreEqual("Error converting value 123 to type 'Newtonsoft.Json.Tests.Converters.StringEnumConverterTests+MyEnum'. Path 'Value', line 1, position 15.", ex.Message); Assert.AreEqual(@"Integer value 123 is not allowed. Path 'Value', line 1, position 15.", ex.InnerException.Message); return; } Assert.Fail(); } #if !NET20 [Test] public void EnumMemberPlusFlags() { List lfoo = new List { Foo.Bat | Foo.SerializeAsBaz, Foo.FooBar, Foo.Bat, Foo.SerializeAsBaz, Foo.FooBar | Foo.SerializeAsBaz, (Foo)int.MaxValue }; string json1 = JsonConvert.SerializeObject(lfoo, Formatting.Indented, new StringEnumConverter { CamelCaseText = true }); StringAssert.AreEqual(@"[ ""Bat, baz"", ""foo_bar"", ""Bat"", ""baz"", ""foo_bar, baz"", 2147483647 ]", json1); IList foos = JsonConvert.DeserializeObject>(json1); Assert.AreEqual(6, foos.Count); Assert.AreEqual(Foo.Bat | Foo.SerializeAsBaz, foos[0]); Assert.AreEqual(Foo.FooBar, foos[1]); Assert.AreEqual(Foo.Bat, foos[2]); Assert.AreEqual(Foo.SerializeAsBaz, foos[3]); Assert.AreEqual(Foo.FooBar | Foo.SerializeAsBaz, foos[4]); Assert.AreEqual((Foo)int.MaxValue, foos[5]); List lbar = new List() { Bar.FooBar, Bar.Bat, Bar.SerializeAsBaz }; string json2 = JsonConvert.SerializeObject(lbar, Formatting.Indented, new StringEnumConverter { CamelCaseText = true }); StringAssert.AreEqual(@"[ ""foo_bar"", ""Bat"", ""baz"" ]", json2); IList bars = JsonConvert.DeserializeObject>(json2); Assert.AreEqual(3, bars.Count); Assert.AreEqual(Bar.FooBar, bars[0]); Assert.AreEqual(Bar.Bat, bars[1]); Assert.AreEqual(Bar.SerializeAsBaz, bars[2]); } [Test] public void DuplicateNameEnumTest() { ExceptionAssert.Throws( () => JsonConvert.DeserializeObject("'foo_bar'", new StringEnumConverter()), @"Error converting value ""foo_bar"" to type 'Newtonsoft.Json.Tests.Converters.DuplicateNameEnum'. Path '', line 1, position 9."); } // Define other methods and classes here [Flags] [JsonConverter(typeof(StringEnumConverter))] private enum Foo { [EnumMember(Value = "foo_bar")] FooBar = 0x01, Bat = 0x02, [EnumMember(Value = "baz")] SerializeAsBaz = 0x4, } [JsonConverter(typeof(StringEnumConverter))] private enum Bar { [EnumMember(Value = "foo_bar")] FooBar, Bat, [EnumMember(Value = "baz")] SerializeAsBaz } [Test] public void DataContractSerializerDuplicateNameEnumTest() { MemoryStream ms = new MemoryStream(); var s = new DataContractSerializer(typeof(DuplicateEnumNameTestClass)); ExceptionAssert.Throws(() => { s.WriteObject(ms, new DuplicateEnumNameTestClass { Value = DuplicateNameEnum.foo_bar, Value2 = DuplicateNameEnum2.foo_bar_NOT_USED }); string xml = @" foo_bar foo_bar "; var o = (DuplicateEnumNameTestClass)s.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(xml))); Assert.AreEqual(DuplicateNameEnum.foo_bar, o.Value); Assert.AreEqual(DuplicateNameEnum2.FooBar, o.Value2); }, "Type 'Newtonsoft.Json.Tests.Converters.DuplicateNameEnum' contains two members 'foo_bar' 'and 'FooBar' with the same name 'foo_bar'. Multiple members with the same name in one type are not supported. Consider changing one of the member names using EnumMemberAttribute attribute."); } [Test] public void EnumMemberWithNumbers() { StringEnumConverter converter = new StringEnumConverter(); NumberNamesEnum e = JsonConvert.DeserializeObject("\"1\"", converter); Assert.AreEqual(NumberNamesEnum.second, e); e = JsonConvert.DeserializeObject("\"2\"", converter); Assert.AreEqual(NumberNamesEnum.first, e); e = JsonConvert.DeserializeObject("\"3\"", converter); Assert.AreEqual(NumberNamesEnum.third, e); e = JsonConvert.DeserializeObject("\"-4\"", converter); Assert.AreEqual(NumberNamesEnum.fourth, e); } [Test] public void EnumMemberWithNumbers_NoIntegerValues() { StringEnumConverter converter = new StringEnumConverter { AllowIntegerValues = false }; NumberNamesEnum e = JsonConvert.DeserializeObject("\"1\"", converter); Assert.AreEqual(NumberNamesEnum.second, e); e = JsonConvert.DeserializeObject("\"2\"", converter); Assert.AreEqual(NumberNamesEnum.first, e); e = JsonConvert.DeserializeObject("\"3\"", converter); Assert.AreEqual(NumberNamesEnum.third, e); e = JsonConvert.DeserializeObject("\"-4\"", converter); Assert.AreEqual(NumberNamesEnum.fourth, e); } #endif [Test] public void AllowIntegerValueAndStringNumber() { JsonSerializationException ex = ExceptionAssert.Throws(() => { JsonConvert.DeserializeObject("\"1\"", new StringEnumConverter { AllowIntegerValues = false }); }); Assert.AreEqual("Integer string '1' is not allowed.", ex.InnerException.Message); } [Test] public void AllowIntegerValueAndNegativeStringNumber() { JsonSerializationException ex = ExceptionAssert.Throws(() => { JsonConvert.DeserializeObject("\"-1\"", new StringEnumConverter { AllowIntegerValues = false }); }); Assert.AreEqual("Integer string '-1' is not allowed.", ex.InnerException.Message); } [Test] public void AllowIntegerValueAndPositiveStringNumber() { JsonSerializationException ex = ExceptionAssert.Throws(() => { JsonConvert.DeserializeObject("\"+1\"", new StringEnumConverter { AllowIntegerValues = false }); }); Assert.AreEqual("Integer string '+1' is not allowed.", ex.InnerException.Message); } [Test] public void AllowIntegerValueAndDash() { JsonSerializationException ex = ExceptionAssert.Throws(() => { JsonConvert.DeserializeObject("\"-\"", new StringEnumConverter { AllowIntegerValues = false }); }); Assert.AreEqual("Requested value '-' was not found.", ex.InnerException.Message); } [Test] public void AllowIntegerValueAndNonNamedValue() { ExceptionAssert.Throws(() => { JsonConvert.SerializeObject((StoreColor)999, new StringEnumConverter { AllowIntegerValues = false }); }, "Integer value 999 is not allowed. Path ''."); } public enum EnumWithDifferentCases { M, m } [Test] public void SerializeEnumWithDifferentCases() { string json = JsonConvert.SerializeObject(EnumWithDifferentCases.M, new StringEnumConverter()); Assert.AreEqual(@"""M""", json); json = JsonConvert.SerializeObject(EnumWithDifferentCases.m, new StringEnumConverter()); Assert.AreEqual(@"""m""", json); } [Test] public void DeserializeEnumWithDifferentCases() { EnumWithDifferentCases e = JsonConvert.DeserializeObject(@"""M""", new StringEnumConverter()); Assert.AreEqual(EnumWithDifferentCases.M, e); e = JsonConvert.DeserializeObject(@"""m""", new StringEnumConverter()); // unfortunatly Enum.Parse with ignoreCase will find the first match rather than the best match // it would be great to find a way around this Assert.AreEqual(EnumWithDifferentCases.M, e); } #if !NET20 [DataContract(Name = "DateFormats")] public enum EnumMemberWithDifferentCases { [EnumMember(Value = "M")] Month, [EnumMember(Value = "m")] Minute } [Test] public void SerializeEnumMemberWithDifferentCases() { string json = JsonConvert.SerializeObject(EnumMemberWithDifferentCases.Month, new StringEnumConverter()); Assert.AreEqual(@"""M""", json); json = JsonConvert.SerializeObject(EnumMemberWithDifferentCases.Minute, new StringEnumConverter()); Assert.AreEqual(@"""m""", json); } [Test] public void DeserializeEnumMemberWithDifferentCases() { EnumMemberWithDifferentCases e = JsonConvert.DeserializeObject(@"""M""", new StringEnumConverter()); Assert.AreEqual(EnumMemberWithDifferentCases.Month, e); e = JsonConvert.DeserializeObject(@"""m""", new StringEnumConverter()); Assert.AreEqual(EnumMemberWithDifferentCases.Minute, e); } #endif } #if !NET20 [DataContract] public class DuplicateEnumNameTestClass { [DataMember] public DuplicateNameEnum Value { get; set; } [DataMember] public DuplicateNameEnum2 Value2 { get; set; } } [DataContract] public enum NumberNamesEnum { [EnumMember(Value = "2")] first, [EnumMember(Value = "1")] second, [EnumMember(Value = "3")] third, [EnumMember(Value = "-4")] fourth } [DataContract] public enum DuplicateNameEnum { [EnumMember] first = 0, [EnumMember] foo_bar = 1, [EnumMember(Value = "foo_bar")] FooBar = 2, [EnumMember] foo_bar_NOT_USED = 3 } [DataContract] public enum DuplicateNameEnum2 { [EnumMember] first = 0, [EnumMember(Value = "foo_bar")] FooBar = 1, [EnumMember] foo_bar = 2, [EnumMember(Value = "TEST")] foo_bar_NOT_USED = 3 } #endif }