-Added EnumMemberAttribute support to StringEnumConverter

This commit is contained in:
JamesNK
2011-03-25 23:49:13 +00:00
parent 39056de238
commit dffe08021d
3 changed files with 173 additions and 15 deletions
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Newtonsoft.Json.Converters;
using NUnit.Framework;
@@ -17,6 +18,11 @@ namespace Newtonsoft.Json.Tests.Converters
public StoreColor? NullableStoreColor2 { get; set; }
}
public class EnumContainer<T>
{
public T Enum { get; set; }
}
public enum NegativeEnum
{
Negative = -1,
@@ -24,12 +30,88 @@ namespace Newtonsoft.Json.Tests.Converters
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
}
#endif
public class NegativeEnumClass
{
public NegativeEnum Value1 { get; set; }
public NegativeEnum Value2 { get; set; }
}
#if !NET20
[Test]
[ExpectedException(typeof(Exception), ExpectedMessage = "Enum name 'Third' already exists on enum 'NamedEnumDuplicate'.")]
public void NamedEnumDuplicateTest()
{
EnumContainer<NamedEnumDuplicate> c = new EnumContainer<NamedEnumDuplicate>
{
Enum = NamedEnumDuplicate.First
};
JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter());
}
[Test]
public void SerializeNameEnumTest()
{
EnumContainer<NamedEnum> c = new EnumContainer<NamedEnum>
{
Enum = NamedEnum.First
};
string json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter());
Assert.AreEqual(@"{
""Enum"": ""@first""
}", json);
c = new EnumContainer<NamedEnum>
{
Enum = NamedEnum.Third
};
json = JsonConvert.SerializeObject(c, Formatting.Indented, new StringEnumConverter());
Assert.AreEqual(@"{
""Enum"": ""Third""
}", json);
}
[Test]
public void DeserializeNameEnumTest()
{
string json = @"{
""Enum"": ""@first""
}";
EnumContainer<NamedEnum> c = JsonConvert.DeserializeObject<EnumContainer<NamedEnum>>(json, new StringEnumConverter());
Assert.AreEqual(NamedEnum.First, c.Enum);
json = @"{
""Enum"": ""Third""
}";
c = JsonConvert.DeserializeObject<EnumContainer<NamedEnum>>(json, new StringEnumConverter());
Assert.AreEqual(NamedEnum.Third, c.Enum);
}
#endif
[Test]
public void SerializeEnumClass()
{
@@ -24,22 +24,31 @@
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Converters
{
/// <summary>
/// Converts an <see cref="Enum"/> to and from its name string value.
/// </summary>
/// <summary>
/// Converts an <see cref="Enum"/> to and from its name string value.
/// </summary>
public class StringEnumConverter : JsonConverter
{
private readonly Dictionary<Type, BidirectionalDictionary<string, string>> _enumMemberNamesPerType = new Dictionary<Type, BidirectionalDictionary<string, string>>();
/// <summary>
/// Gets or sets a value indicating whether the written enum text should be camel case.
/// </summary>
/// <value><c>true</c> if the written enum text will be camel case; otherwise, <c>false</c>.</value>
public bool CamelCaseText { get; set; }
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
@@ -54,21 +63,26 @@ namespace Newtonsoft.Json.Converters
return;
}
Enum e = (Enum) value;
Enum e = (Enum)value;
string enumName = e.ToString("G");
if (char.IsNumber(enumName[0]) || enumName[0] == '-')
{
// enum value didn't have an option against it
// fallback to writing number value
writer.WriteValue(value);
}
else
{
if (CamelCaseText)
enumName = StringUtils.ToCamelCase(enumName);
BidirectionalDictionary<string, string> map = GetEnumNameMap(e.GetType());
writer.WriteValue(enumName);
string resolvedEnumName;
map.TryGetByFirst(enumName, out resolvedEnumName);
resolvedEnumName = resolvedEnumName ?? enumName;
if (CamelCaseText)
resolvedEnumName = StringUtils.ToCamelCase(resolvedEnumName);
writer.WriteValue(resolvedEnumName);
}
}
@@ -83,8 +97,8 @@ namespace Newtonsoft.Json.Converters
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Type t = (ReflectionUtils.IsNullableType(objectType))
? Nullable.GetUnderlyingType(objectType)
: objectType;
? Nullable.GetUnderlyingType(objectType)
: objectType;
if (reader.TokenType == JsonToken.Null)
{
@@ -95,26 +109,84 @@ namespace Newtonsoft.Json.Converters
}
if (reader.TokenType == JsonToken.String)
return Enum.Parse(t, reader.Value.ToString(), true);
{
var map = GetEnumNameMap(t);
string resolvedEnumName;
map.TryGetBySecond(reader.Value.ToString(), out resolvedEnumName);
resolvedEnumName = resolvedEnumName ?? reader.Value.ToString();
return Enum.Parse(t, resolvedEnumName, true);
}
if (reader.TokenType == JsonToken.Integer)
return ConvertUtils.ConvertOrCast(reader.Value, CultureInfo.InvariantCulture, t);
throw new Exception("Unexpected token when parsing enum. Expected String or Integer, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
}
/// <summary>
/// A cached representation of the Enum string representation to respect per Enum field name.
/// </summary>
/// <param name="t">The type of the Enum.</param>
/// <returns>A map of enum field name to either the field name, or the configured enum member name (<see cref="EnumMemberAttribute"/>).</returns>
private BidirectionalDictionary<string, string> GetEnumNameMap(Type t)
{
BidirectionalDictionary<string, string> map;
if (!_enumMemberNamesPerType.TryGetValue(t, out map))
{
lock (_enumMemberNamesPerType)
{
if (_enumMemberNamesPerType.TryGetValue(t, out map))
return map;
map = new BidirectionalDictionary<string, string>(
StringComparer.OrdinalIgnoreCase,
StringComparer.OrdinalIgnoreCase);
foreach (FieldInfo f in t.GetFields())
{
string n1 = f.Name;
string n2;
#if !NET20
n2 = f.GetCustomAttributes(typeof (EnumMemberAttribute), true)
.Cast<EnumMemberAttribute>()
.Select(a => a.Value)
.SingleOrDefault() ?? f.Name;
#else
n2 = f.Name;
#endif
string s;
if (map.TryGetBySecond(n2, out s))
{
throw new Exception("Enum name '{0}' already exists on enum '{1}'."
.FormatWith(CultureInfo.InvariantCulture, n2, t.Name));
}
map.Add(n1, n2);
}
_enumMemberNamesPerType[t] = map;
}
}
return map;
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
Type t = (ReflectionUtils.IsNullableType(objectType))
? Nullable.GetUnderlyingType(objectType)
: objectType;
? Nullable.GetUnderlyingType(objectType)
: objectType;
return t.IsEnum;
}
+4
View File
@@ -177,12 +177,15 @@ namespace Newtonsoft.Json.Linq
return b1.CompareTo(b2);
case JTokenType.Date:
#if !NET20
if (objA is DateTime)
{
#endif
DateTime date1 = Convert.ToDateTime(objA, CultureInfo.InvariantCulture);
DateTime date2 = Convert.ToDateTime(objB, CultureInfo.InvariantCulture);
return date1.CompareTo(date2);
#if !NET20
}
else
{
@@ -194,6 +197,7 @@ namespace Newtonsoft.Json.Linq
return date1.CompareTo(date2);
}
#endif
case JTokenType.Bytes:
if (!(objB is byte[]))
throw new ArgumentException("Object must be of type byte[].");