DataContractJsonSerializer和枚举类型

45

当我使用DataContractJsonSerializer序列化枚举值时,它会序列化枚举的数值,而不是字符串名称。

例如:

enum foo
{
   bar,
   baz
}

将foo.bar的值序列化返回"0",而不是"bar"。我希望反过来,请问是否有方法可以覆盖这一默认行为?

编辑:

因为我不想更改序列化器,所以我使用了一个简单的解决方法。

我在类中公开了一个属性来序列化调用该值的ToString方法,例如:

// Old
[DataMember]
public EnumType Foo
{
    get { return _foo; }
    set { _foo = value; }
}

// New, I still kept the EnumType but I only serialize the string version

public EnumType Foo
{
    get { return _foo; }
    set { _foo = value; }
}

[DataMember]
public string FooType
{
    get { return _foo.ToString(); }
    private set {}
}

这并不太令人意外,因为枚举默认为 int 类型。 - Powerlord
在XML序列化中,建议避免在WCF数据契约中公开枚举类型,因为它们会创建微妙的向后兼容性问题。请参见https://dev59.com/rnRC5IYBdhLWcg3wVvnL。 - Michael Freidgeim
5个回答

27

看起来这是设计上的问题,这个行为无法更改:

在 JSON 中,枚举成员值被视为数字,这与数据合同中对待它们的方式不同,在数据合同中,它们作为成员名称包含在内。

以下是使用一种替代方案(我认为更好且更易扩展)的示例序列化程序,可以实现您要求的功能:

using System;
using Newtonsoft.Json;

class Program
{
    static void Main(string[] args)
    {
        var baz = Foo.Baz;
        var serializer = new JsonSerializer();
        serializer.Converters.Add(new JsonEnumTypeConverter());
        serializer.Serialize(Console.Out, baz);
        Console.WriteLine();
    }
}

enum Foo
{
    Bar,
    Baz
}

public class JsonEnumTypeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Foo);
    }
    public override void WriteJson(JsonWriter writer, object value)
    {
        writer.WriteValue(((Foo)value).ToString());
    }

    public override object ReadJson(JsonReader reader, Type objectType)
    {
        return Enum.Parse(typeof(Foo), reader.Value.ToString());
    }
}

8
我认为现在做这件事的新方法是 jsonSerializer.Converters.Add(new StringEnumConverter()); - 从4.5版本开始。 - shanabus
1
@shanabus - 在WCF服务中,您在哪里添加那个神奇的代码行?(问题已标记为WCF) - BornToCode
@BornToCode,我对wcf不是很熟悉,也不是通过这种方式找到这个问题的。这个回答是否解决了你的问题 - http://stackoverflow.com/questions/6642961/can-i-use-json-serialization-in-wcf-service? - shanabus

14
我曾试图找到一个优雅的解决方案来解决这个问题,因为似乎每个人都默认使用Newtonsoft的序列化器来解决这个问题。虽然Newtonsoft提供了更多功能,但它确实有一些严重的缺点。其中几个是:需要无参数构造函数,如果您希望序列化实现IEnumerable接口的类,则行为异常,而且在使用抽象类型时性能非常差(因为它不使用KnownTypes属性,而且解决方法会生成冗长的输出,向调用者公开您的内部命名空间)。
另一方面,在使用MVC4 WebApi解决方案时,很少有关于如何自定义DataContractJsonSerializer的示例。
我花了一段时间才找到一个解决方案,它将枚举表示为字符串,并解决了使用DataContractJsonSerializer时出现的已知DateTime格式问题。
第一部分-将这些扩展方法放入扩展类中
#region JSon

    /// <summary>Serializes an object to JSon.</summary>
    /// <param name="obj">The object to serialize.</param>
    /// <returns>Returns a byte array with the serialized object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static byte[] SerializeJson(this object obj)
    {
        using (MemoryStream b = new MemoryStream())
        {
            SerializeJson(obj, b);
            return b.ToArray();
        }
    }

    /// <summary>Serializes an object to JSon.</summary>
    /// <param name="obj">The object to serialize.</param>
    /// <param name="stream">The stream to write to.</param>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static void SerializeJson(this object obj, Stream stream)
    {
        var settings = new DataContractJsonSerializerSettings();
        settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        settings.DataContractSurrogate = new EnumToStringDataContractSurrogate();

        var type = obj == null ? typeof(object) : obj.GetType();

        var enumerationValue = obj as System.Collections.IEnumerable;

        var fixedValue = enumerationValue != null
                         ? type.IsGenericType && !type.GetGenericArguments()[0].IsInterface
                            ? enumerationValue.ToArray(type.GetGenericArguments()[0])
                            : enumerationValue.OfType<object>().ToArray()
                         : obj;

        if (enumerationValue != null && (!type.IsGenericType || (type.IsGenericType || type.GetGenericArguments()[0].IsInterface)))
        {
            var firstMember = (fixedValue as System.Collections.IEnumerable).OfType<object>().FirstOrDefault();
            if (firstMember != null)
                fixedValue = enumerationValue.ToArray(firstMember.GetType());
        }

        var fixedType = obj == null
                        ? type
                        : fixedValue.GetType();

        var jsonSer = new DataContractJsonSerializer(fixedType, settings);
        jsonSer.WriteObject(stream, fixedValue);
    }

    /// <summary>
    /// Deserializes an object.
    /// </summary>
    /// <typeparam name="T">The output type of the object.</typeparam>
    /// <param name="data">The serialized contents.</param>
    /// <returns>Returns the typed deserialized object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static T DeserializeJSon<T>(this byte[] data)
    {
        using (MemoryStream b = new MemoryStream(data))
            return DeserializeJSon<T>(b);
    }

    /// <summary>Deserializes a JSon object.</summary>
    /// <typeparam name="T">The output type of the object.</typeparam>
    /// <param name="stream">The stream to read from.</param>
    /// <returns>Returns the typed object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static T DeserializeJSon<T>(this Stream stream)
    {
        var settings = new DataContractJsonSerializerSettings();
        settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        settings.DataContractSurrogate = new EnumToStringDataContractSurrogate();

        var jsonSer = new DataContractJsonSerializer(typeof(T), settings);
        return (T)jsonSer.ReadObject(stream);
    }

    /// <summary>Deserializes a JSon object.</summary>
    /// <param name="data">The serialized contents.</param>
    /// <param name="targetType">The target type.</param>
    /// <returns>Returns the typed deserialized object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static object DeserializeJSon(this byte[] data, Type targetType)
    {
        using (MemoryStream b = new MemoryStream(data))
        {
            return DeserializeJSon(b, targetType);
        }
    }

    /// <summary>Deserializes a JSon object.</summary>
    /// <param name="data">The serialized contents.</param>
    /// <param name="targetType">The target type.</param>
    /// <returns>Returns the typed deserialized object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static object DeserializeJSon(this Stream data, Type targetType)
    {
        var settings = new DataContractJsonSerializerSettings();
        settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        settings.DataContractSurrogate = new EnumToStringDataContractSurrogate();

        var jsonSer = new DataContractJsonSerializer(targetType, settings);
        return jsonSer.ReadObject(data);            
    }

    /// <summary>Enumerator contract surrogate.</summary>
    internal class EnumToStringDataContractSurrogate : IDataContractSurrogate
    {
        Type IDataContractSurrogate.GetDataContractType(Type type)
        {
            return type == typeof(Enum) ? typeof(string) : type;
        }

        object IDataContractSurrogate.GetDeserializedObject(object obj, Type targetType)
        {
            if (targetType.IsEnum)
            {
                return obj == null
                       ? System.Enum.GetValues(targetType).OfType<int>().FirstOrDefault()
                       : System.Enum.Parse(targetType, obj.ToString());
            }
            return obj;
        }

        object IDataContractSurrogate.GetObjectToSerialize(object obj, Type targetType)
        {
            if (obj is Enum)
            {
                var pair = Enum.GetName(obj.GetType(), obj);
                return pair;
            }

            return obj;
        }

        object IDataContractSurrogate.GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            throw new NotImplementedException();
        }

        object IDataContractSurrogate.GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
        {
            throw new NotImplementedException();
        }

        void IDataContractSurrogate.GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
        {
            throw new NotImplementedException();
        }

        Type IDataContractSurrogate.GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            throw new NotImplementedException();
        }

        System.CodeDom.CodeTypeDeclaration IDataContractSurrogate.ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            throw new NotImplementedException();
        }
    }

    #endregion


    /// <summary>Creates an array from a non generic source.</summary>
    /// <param name="source">The source.</param>
    /// <param name="type">The target type of the array.</param>
    /// <returns>Returns a typed array.</returns>
    public static Array ToArray(this IEnumerable source, Type type)
    {
        var param = Expression.Parameter(typeof(IEnumerable), "source");
        var cast = Expression.Call(typeof(Enumerable), "Cast", new[] { type }, param);
        var toArray = Expression.Call(typeof(Enumerable), "ToArray", new[] { type }, cast);
        var lambda = Expression.Lambda<Func<IEnumerable, Array>>(toArray, param).Compile();

        return lambda(source);
    }

第二部分 - 通过封装DataContractJsonSerializer来创建自己的格式化程序 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// <summary>Custom implementation of DataContract formatter.</summary>
public class DataContractJsonFormatter : MediaTypeFormatter
{

    /// <summary>Creates a new instance.</summary>
    public DataContractJsonFormatter()
    {
        SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
    }

    /// <summary>Gets if the formatter the write a given type.</summary>
    /// <param name="type">The type to handle.</param>
    /// <returns>Returns if the formatter the write a given type.</returns>
    public override bool CanWriteType(Type type)
    {
        return true;
    }

    /// <summary>Gets if the formatter the read a given type.</summary>
    /// <param name="type">The type to handle.</param>
    /// <returns>Returns if the formatter the read a given type.</returns>
    public override bool CanReadType(Type type)
    {
        return true;
    }

    /// <summary>Deserializes an object.</summary>
    /// <param name="type">The target type.</param>
    /// <param name="readStream">The stream to read from.</param>
    /// <param name="content">The http content.</param>
    /// <param name="formatterLogger">A logger.</param>
    /// <returns>Returns the deserialized object.</returns>
    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
    {
        var task = Task<object>.Factory.StartNew(() =>
        {
            return readStream.DeserializeJSon(type);
        });

        return task;
    }

    /// <summary>Serializes an object.</summary>
    /// <param name="type">The target type.</param>
    /// <param name="value">The object to serialize.</param>
    /// <param name="writeStream">The stream to write to.</param>
    /// <param name="content">The http content.</param>
    /// <param name="transportContext">The context.</param>
    /// <returns>Returns the deserialized object.</returns>
    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
    {
        var task = Task.Factory.StartNew(() =>
        {
            value.SerializeJson(writeStream);
        });

        return task;
    }
}

第三部分 - 编辑您的Global.asax文件并使用新的JSon格式化程序 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    /// <summary>Event handlers of when the application starts.</summary>
    [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
    protected void Application_Start()
    {
       //Register my custom DataContract JSon serializer
        GlobalConfiguration.Configuration.Formatters.Insert(0, new DataContractJsonFormatter());

        //Register areas
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
       // BundleConfig.RegisterBundles(BundleTable.Bundles);

        //JSON serialization config
        var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
        json.UseDataContractJsonSerializer = false;
    }

12

要获得WCF JSON的双向序列化/反序列化,您可以添加一个类型为字符串的第二个get set属性,在JavaScript中构建JSON对象时使用名为string的属性,在服务器端使用强类型枚举版本,例如:

public class DTOSearchCriteria {
  public int ? ManufacturerID { get; set; }
  public int ? ModelID { get; set; }

  private SortBy _sort;

  public SortBy SortType {
    get { return _sort; }
    set { _sort = value; }
  }

  public String Sort {
    get {
      return _sort.ToString();
    }
    set {
      _sort = (SortBy) Enum.Parse(typeof(SortBy), value);
    }
  }

  public int PageSize { get; set; }
  public int PageNumber { get; set; }
}

public enum SortBy {
  PriceDescending,
  PriceAscending
}

虽然这样做很丑陋。微软强制实施这种类型的实现真是一种耻辱。 - crush

4

编辑:抱歉刚起床还没有喝咖啡 :(

这里是使用Json Serializer实现你想要的功能的代码,而不是DataContractJsonSerializer。

我还没有使用DataContractJsonSerializer进行任何工作,但是在快速浏览文档后,我对微软感到非常失望。他们显然极力使WCF非常可扩展,但是在DataContractJsonSerializer中却没有可扩展性。你必须使用微软风格的JSON。微软太糟糕了...已经搞砸了WCF。

    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    using System.Web.Script.Serialization;

一些测试对象和枚举:

    public enum SomeSillyEnum
    {
        Foo,Bar,Doo,Daa,Dee
    }

    public class UseSillyEnum
    {
        public SomeSillyEnum PublicEnum { get; set; }
        public string SomeOtherProperty { get; set; }
        public UseSillyEnum()
        {
            PublicEnum = SomeSillyEnum.Foo;
            SomeOtherProperty = "Testing";
        }
    }

JavaScriptConverters。一个用于所有枚举类型的转换器,以及一个用于使用枚举类型的对象的转换器。

public class EnumStringConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        foreach(string key in dictionary.Keys)
        {
            try { return Enum.Parse(type, dictionary[key].ToString(), false); }
            catch(Exception ex) { throw new SerializationException("Problem trying to deserialize enum from JSON.",ex); }
        }
        return Activator.CreateInstance(type);
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        Dictionary<string,object> objs = new Dictionary<string, object>();
        objs.Add(obj.ToString(), ((Enum)obj).ToString("D"));
        return objs;
    }

    public override IEnumerable<Type> SupportedTypes{get {return new Type[] {typeof (Enum)};}}
}

public class SillyConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        UseSillyEnum se = new UseSillyEnum();
        foreach (string key in dictionary.Keys)
        {
            switch(key)
            {
                case "PublicEnum":
                    se.PublicEnum = (SomeSillyEnum) Enum.Parse(typeof (SomeSillyEnum), dictionary[key].ToString(), false);
                    break;
                case "SomeOtherProperty":
                    se.SomeOtherProperty = dictionary[key].ToString();
                    break;
            }
        }
        return se;
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        UseSillyEnum se = (UseSillyEnum)obj;
        Dictionary<string, object> objs = new Dictionary<string, object>();
        objs.Add("PublicEnum", se.PublicEnum);
        objs.Add("SomeOtherProperty", se.SomeOtherProperty);
        return objs;
    }

    public override IEnumerable<Type> SupportedTypes { get { return new Type[] { typeof(UseSillyEnum) }; } }
}

并在页面内使用它:

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        /* Handles ALL Enums

        JavaScriptSerializer jsonSer = new JavaScriptSerializer(); 
        jsonSer.RegisterConverters( new JavaScriptConverter[] { new EnumStringConverter() } );

        string json = jsonSer.Serialize(new UseSillyEnum());
        Response.Write(json);

        UseSillyEnum obj = jsonSer.Deserialize<UseSillyEnum>(json);
        Response.Write(obj.PublicEnum);

        */

        /* Handles Object that uses an enum */
        JavaScriptSerializer jsonSer = new JavaScriptSerializer();
        jsonSer.RegisterConverters( new JavaScriptConverter[] { new SillyConverter() } );
        string json = jsonSer.Serialize(new UseSillyEnum());
        Response.Write(json);

        UseSillyEnum obj = jsonSer.Deserialize<UseSillyEnum>(json);
        Response.Write(obj.PublicEnum);
    }
}

1
我已经使用Newtonsoft.Json库将此解决方案的所有部分组合在一起,以一种适用于WCF的方式。它解决了枚举问题,并使错误处理更加完善,在IIS托管服务中也能正常工作。由于代码量很大,因此您可以在GitHub上找到它:https://github.com/jongrant/wcfjsonserializer/blob/master/NewtonsoftJsonFormatter.cs 要使其正常工作,您必须向Web.config添加一些条目,您可以在此处查看示例文件:https://github.com/jongrant/wcfjsonserializer/blob/master/Web.config

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接