DataContractJsonSerializer 和枚举

Posted

技术标签:

【中文标题】DataContractJsonSerializer 和枚举【英文标题】:DataContractJsonSerializer and Enums 【发布时间】:2010-10-22 03:28:02 【问题描述】:

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

IE:

enum foo

   bar,
   baz

序列化 foo.bar 的值返回“0”,而不是“bar”。

我更喜欢它,有没有办法覆盖它?

编辑:

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

我在类中公开了一个属性来序列化,它在值上调用 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 类型。 在 XML 序列化中,还建议避免在 wcf 数据协定中公开枚举,因为它们会产生微妙的向后兼容问题。见***.com/questions/326339/… 【参考方案1】:

It looks like this is by design 并且此行为无法更改:

枚举成员值被处理 作为 JSON 中的数字,这是不同的 从它们在数据中的处理方式 合同,其中它们被包括为 成员名称。

这是一个使用 an alternative(以及 IMO 更好和更可扩展)序列化程序的示例,它可以实现您正在寻找的内容:

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());
    

【讨论】:

我相信新的方法是 jsonSerializer.Converters.Add(new StringEnumConverter()); - 从 4.5 开始 @shanabus - 你在 wcf 服务中的什么地方添加了这条神奇的线? (问题被标记为 wcf) @BornToCode 我对wcf 不是很熟悉,这不是我发现这个问题的方式。这是否回答了您的问题 - ***.com/questions/6642961/… ?【参考方案2】:

我疯狂地试图找到一个优雅的解决方案来解决这个问题,因为似乎每个人都默认使用 Newtonsoft 的序列化程序来解决这个问题。 尽管 Newtonsoft 提供了更多功能,但它确实有一些严重的缺点。 举几个例子:需要无参数的构造函数,如果你想序列化实现 IEnumerable 的类的疯狂行为,并且在使用抽象类型时表现非常糟糕(因为它不使用 KnownTypes 属性,解决方法会生成一个将内部命名空间暴露给调用者的详细输出)。

另一方面,在 MVC4 WebApi 解决方案上使用 DataContractJsonSerializer 时如何自定义它的例子很少。

我花了一段时间才找到将枚举表示为字符串并解决 DataContractJsonSerializer 附带的已知 DateTime 格式问题的解决方案。

第 I 部分 - 将这些扩展方法放在扩展类中 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~

#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;
    

【讨论】:

【参考方案3】:

要获得 wcf json 的 2 路序列化/反序列化,您可以添加第二个字符串类型的 get set 属性,当您在 javascript 中构建 json 对象时,请使用 string 命名属性,在服务器端使用强类型枚举版本:eg

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

【讨论】:

虽然丑得要命。微软强迫这种类型的实施真是太丢脸了。【参考方案4】:

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

这是使用 Json 序列化器而不是 DataContractJsonSerializer 执行您想要执行的操作的代码。

我还没有使用 DataContractJsonSerializer 完成任何工作,但是在快速扫描文档后,我对 MS 相当失望。他们显然走极端使 WCF 非常可扩展,但是使用 DataContractJsonSerializer 没有可扩展性。你必须使用 MS 风格的 JSON。 MS 的超级蹩脚......已经搞砸了 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";
        
    

JavaScript 转换器。一个用于所有枚举,一个用于使用枚举的对象。

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> SupportedTypesget 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);
    

【讨论】:

【参考方案5】:

我使用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

【讨论】:

以上是关于DataContractJsonSerializer 和枚举的主要内容,如果未能解决你的问题,请参考以下文章