-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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
using System.Text; using System.Text;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using NUnit.Framework; using NUnit.Framework;
@@ -17,6 +18,11 @@ namespace Newtonsoft.Json.Tests.Converters
public StoreColor? NullableStoreColor2 { get; set; } public StoreColor? NullableStoreColor2 { get; set; }
} }
public class EnumContainer<T>
{
public T Enum { get; set; }
}
public enum NegativeEnum public enum NegativeEnum
{ {
Negative = -1, Negative = -1,
@@ -24,12 +30,88 @@ namespace Newtonsoft.Json.Tests.Converters
Positive = 1 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 class NegativeEnumClass
{ {
public NegativeEnum Value1 { get; set; } public NegativeEnum Value1 { get; set; }
public NegativeEnum Value2 { 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] [Test]
public void SerializeEnumClass() public void SerializeEnumClass()
{ {
@@ -24,22 +24,31 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json.Utilities; using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Converters namespace Newtonsoft.Json.Converters
{ {
/// <summary>
/// Converts an <see cref="Enum"/> to and from its name string value.
/// </summary>
/// <summary> /// <summary>
/// Converts an <see cref="Enum"/> to and from its name string value. /// Converts an <see cref="Enum"/> to and from its name string value.
/// </summary> /// </summary>
public class StringEnumConverter : JsonConverter public class StringEnumConverter : JsonConverter
{ {
private readonly Dictionary<Type, BidirectionalDictionary<string, string>> _enumMemberNamesPerType = new Dictionary<Type, BidirectionalDictionary<string, string>>();
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the written enum text should be camel case. /// Gets or sets a value indicating whether the written enum text should be camel case.
/// </summary> /// </summary>
/// <value><c>true</c> if the written enum text will be camel case; otherwise, <c>false</c>.</value> /// <value><c>true</c> if the written enum text will be camel case; otherwise, <c>false</c>.</value>
public bool CamelCaseText { get; set; } public bool CamelCaseText { get; set; }
/// <summary> /// <summary>
/// Writes the JSON representation of the object. /// Writes the JSON representation of the object.
/// </summary> /// </summary>
@@ -54,21 +63,26 @@ namespace Newtonsoft.Json.Converters
return; return;
} }
Enum e = (Enum) value; Enum e = (Enum)value;
string enumName = e.ToString("G"); string enumName = e.ToString("G");
if (char.IsNumber(enumName[0]) || enumName[0] == '-') if (char.IsNumber(enumName[0]) || enumName[0] == '-')
{ {
// enum value didn't have an option against it
// fallback to writing number value
writer.WriteValue(value); writer.WriteValue(value);
} }
else else
{ {
if (CamelCaseText) BidirectionalDictionary<string, string> map = GetEnumNameMap(e.GetType());
enumName = StringUtils.ToCamelCase(enumName);
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) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
Type t = (ReflectionUtils.IsNullableType(objectType)) Type t = (ReflectionUtils.IsNullableType(objectType))
? Nullable.GetUnderlyingType(objectType) ? Nullable.GetUnderlyingType(objectType)
: objectType; : objectType;
if (reader.TokenType == JsonToken.Null) if (reader.TokenType == JsonToken.Null)
{ {
@@ -95,26 +109,84 @@ namespace Newtonsoft.Json.Converters
} }
if (reader.TokenType == JsonToken.String) 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) if (reader.TokenType == JsonToken.Integer)
return ConvertUtils.ConvertOrCast(reader.Value, CultureInfo.InvariantCulture, t); 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)); 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> /// <summary>
/// Determines whether this instance can convert the specified object type. /// Determines whether this instance can convert the specified object type.
/// </summary> /// </summary>
/// <param name="objectType">Type of the object.</param> /// <param name="objectType">Type of the object.</param>
/// <returns> /// <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> /// </returns>
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
Type t = (ReflectionUtils.IsNullableType(objectType)) Type t = (ReflectionUtils.IsNullableType(objectType))
? Nullable.GetUnderlyingType(objectType) ? Nullable.GetUnderlyingType(objectType)
: objectType; : objectType;
return t.IsEnum; return t.IsEnum;
} }
+4
View File
@@ -177,12 +177,15 @@ namespace Newtonsoft.Json.Linq
return b1.CompareTo(b2); return b1.CompareTo(b2);
case JTokenType.Date: case JTokenType.Date:
#if !NET20
if (objA is DateTime) if (objA is DateTime)
{ {
#endif
DateTime date1 = Convert.ToDateTime(objA, CultureInfo.InvariantCulture); DateTime date1 = Convert.ToDateTime(objA, CultureInfo.InvariantCulture);
DateTime date2 = Convert.ToDateTime(objB, CultureInfo.InvariantCulture); DateTime date2 = Convert.ToDateTime(objB, CultureInfo.InvariantCulture);
return date1.CompareTo(date2); return date1.CompareTo(date2);
#if !NET20
} }
else else
{ {
@@ -194,6 +197,7 @@ namespace Newtonsoft.Json.Linq
return date1.CompareTo(date2); return date1.CompareTo(date2);
} }
#endif
case JTokenType.Bytes: case JTokenType.Bytes:
if (!(objB is byte[])) if (!(objB is byte[]))
throw new ArgumentException("Object must be of type byte[]."); throw new ArgumentException("Object must be of type byte[].");