JavaScriptSerializer.Deserialize - 如何更改字段名称

Posted

技术标签:

【中文标题】JavaScriptSerializer.Deserialize - 如何更改字段名称【英文标题】:JavaScriptSerializer.Deserialize - how to change field names 【发布时间】:2010-11-09 04:03:02 【问题描述】:

总结:使用 javascriptSerializer.Deserialize 时,如何将 JSON 数据中的字段名称映射到 .Net 对象的字段名称?

加长版:我从服务器 API 收到以下 JSON 数据(未在 .Net 中编码)

"user_id":1234, "detail_level":"low"

我有以下 C# 对象:

[Serializable]
public class DataObject

    [XmlElement("user_id")]
    public int UserId  get; set; 

    [XmlElement("detail_level")]
    public DetailLevel DetailLevel  get; set; 

其中 DetailLevel 是一个以“Low”作为值之一的枚举。

此测试失败:

[TestMethod]
public void DataObjectSimpleParseTest()

    JavaScriptSerializer serializer = new JavaScriptSerializer();
    DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);

    Assert.IsNotNull(dataObject);
    Assert.AreEqual(DetailLevel.Low, dataObject.DetailLevel);
    Assert.AreEqual(1234, dataObject.UserId);

最后两个断言失败,因为这些字段中没有数据。如果我将 JSON 数据更改为

 "userid":1234, "detaillevel":"low"

然后它通过了。但我无法更改服务器的行为,并且我希望客户端类在 C# 习语中具有命名良好的属性。我不能使用 LINQ to JSON,因为我希望它在 Silverlight 之外工作。看起来 XmlElement 标签没有效果。我完全不知道我从哪里得到它们相关的想法,它们可能不相关。

如何在 JavaScriptSerializer 中进行字段名映射?能做到吗?

【问题讨论】:

我讨厌JavaScriptSerializerJwtSecurityTokenHandler 通过静态 JsonExtensions.Serializer 属性使用它,这意味着在运行时更改它可能会影响其他期望它不变的代码。不幸的是,其中许多课程都是这样的。 :( 【参考方案1】:

我又尝试了一次,使用DataContractJsonSerializer 类。这样就解决了:

代码如下所示:

using System.Runtime.Serialization;

[DataContract]
public class DataObject

    [DataMember(Name = "user_id")]
    public int UserId  get; set; 

    [DataMember(Name = "detail_level")]
    public string DetailLevel  get; set; 

测试是:

using System.Runtime.Serialization.Json;

[TestMethod]
public void DataObjectSimpleParseTest()

        DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(DataObject));

        MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(JsonData));
        DataObject dataObject = serializer.ReadObject(ms) as DataObject;

        Assert.IsNotNull(dataObject);
        Assert.AreEqual("low", dataObject.DetailLevel);
        Assert.AreEqual(1234, dataObject.UserId);

唯一的缺点是我必须将 DetailLevel 从枚举更改为字符串 - 如果将枚举类型保留在适当的位置,DataContractJsonSerializer 预计会读取数值并失败。有关详细信息,请参阅DataContractJsonSerializer and Enums。

在我看来,这很糟糕,尤其是当 JavaScriptSerializer 正确处理它时。这是您尝试将字符串解析为枚举的例外情况:

System.Runtime.Serialization.SerializationException: There was an error deserializing the object of type DataObject. The value 'low' cannot be parsed as the type 'Int64'. --->
System.Xml.XmlException: The value 'low' cannot be parsed as the type 'Int64'. --->  
System.FormatException: Input string was not in a correct format

像这样标记枚举不会改变这种行为:

[DataContract]
public enum DetailLevel

    [EnumMember(Value = "low")]
    Low,
   ...
 

这似乎也适用于 Silverlight。

【讨论】:

很好的解决方案!使用 .Net 4.5 似乎对于仅具有简单 [DataMember] 声明的枚举成员(不需要 [EnumMember] 等)工作正常 您的 JsonData 中有什么?当我按照您所写的那样执行此操作时,我得到一个 SerializationException ,其大意是 Serializer 期待一个根元素,就好像它期待 XML 一样。我的 JSON 数据是 "user": "THEDOMAIN\\MDS", "password": "JJJJ"【参考方案2】:

通过创建自定义JavaScriptConverter,您可以将任何名称映射到任何属性。但它确实需要手动编码地图,这不太理想。

public class DataObjectJavaScriptConverter : JavaScriptConverter

    private static readonly Type[] _supportedTypes = new[]
    
        typeof( DataObject )
    ;

    public override IEnumerable<Type> SupportedTypes 
     
        get  return _supportedTypes;  
    

    public override object Deserialize( IDictionary<string, object> dictionary, 
                                        Type type, 
                                        JavaScriptSerializer serializer )
    
        if( type == typeof( DataObject ) )
        
            var obj = new DataObject();
            if( dictionary.ContainsKey( "user_id" ) )
                obj.UserId = serializer.ConvertToType<int>( 
                                           dictionary["user_id"] );
            if( dictionary.ContainsKey( "detail_level" ) )
                obj.DetailLevel = serializer.ConvertToType<DetailLevel>(
                                           dictionary["detail_level"] );

            return obj;
        

        return null;
    

    public override IDictionary<string, object> Serialize( 
            object obj, 
            JavaScriptSerializer serializer )
    
        var dataObj = obj as DataObject;
        if( dataObj != null )
        
            return new Dictionary<string,object>
            
                "user_id", dataObj.UserId ,
                "detail_level", dataObj.DetailLevel 
            
        
        return new Dictionary<string, object>();
    

然后你可以像这样反序列化:

var serializer = new JavaScriptSerializer();
serialzer.RegisterConverters( new[] new DataObjectJavaScriptConverter()  );
var dataObj = serializer.Deserialize<DataObject>( json );

【讨论】:

【参考方案3】:

Json.NET 会做你想做的事(免责声明:我是包的作者)。它支持读取 DataContract/DataMember 属性以及它自己的属性来更改属性名称。还有 StringEnumConverter 类用于将枚举值序列化为名称而不是数字。

【讨论】:

一个两行代码示例显示使用该属性会很高兴在这个答案中看到。【参考方案4】:

JavaScriptSerializer 中没有对属性重命名的标准支持,但是您可以很容易地添加自己的:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization;
using System.Reflection;

public class JsonConverter : JavaScriptConverter

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    
        List<MemberInfo> members = new List<MemberInfo>();
        members.AddRange(type.GetFields());
        members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));

        object obj = Activator.CreateInstance(type);

        foreach (MemberInfo member in members)
        
            JsonPropertyAttribute jsonProperty = (JsonPropertyAttribute)Attribute.GetCustomAttribute(member, typeof(JsonPropertyAttribute));

            if (jsonProperty != null && dictionary.ContainsKey(jsonProperty.Name))
            
                SetMemberValue(serializer, member, obj, dictionary[jsonProperty.Name]);
            
            else if (dictionary.ContainsKey(member.Name))
            
                SetMemberValue(serializer, member, obj, dictionary[member.Name]);
            
            else
            
                KeyValuePair<string, object> kvp = dictionary.FirstOrDefault(x => string.Equals(x.Key, member.Name, StringComparison.InvariantCultureIgnoreCase));

                if (!kvp.Equals(default(KeyValuePair<string, object>)))
                
                    SetMemberValue(serializer, member, obj, kvp.Value);
                
            
        

        return obj;
    


    private void SetMemberValue(JavaScriptSerializer serializer, MemberInfo member, object obj, object value)
    
        if (member is PropertyInfo)
        
            PropertyInfo property = (PropertyInfo)member;                
            property.SetValue(obj, serializer.ConvertToType(value, property.PropertyType), null);
        
        else if (member is FieldInfo)
        
            FieldInfo field = (FieldInfo)member;
            field.SetValue(obj, serializer.ConvertToType(value, field.FieldType));
        
    


    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    
        Type type = obj.GetType();
        List<MemberInfo> members = new List<MemberInfo>();
        members.AddRange(type.GetFields());
        members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));

        Dictionary<string, object> values = new Dictionary<string, object>();

        foreach (MemberInfo member in members)
        
            JsonPropertyAttribute jsonProperty = (JsonPropertyAttribute)Attribute.GetCustomAttribute(member, typeof(JsonPropertyAttribute));

            if (jsonProperty != null)
            
                values[jsonProperty.Name] = GetMemberValue(member, obj);
            
            else
            
                values[member.Name] = GetMemberValue(member, obj);
            
        

        return values;
    

    private object GetMemberValue(MemberInfo member, object obj)
    
        if (member is PropertyInfo)
        
            PropertyInfo property = (PropertyInfo)member;
            return property.GetValue(obj, null);
        
        else if (member is FieldInfo)
        
            FieldInfo field = (FieldInfo)member;
            return field.GetValue(obj);
        

        return null;
    


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


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class JsonPropertyAttribute : Attribute

    public JsonPropertyAttribute(string name)
    
        Name = name;
    

    public string Name
    
        get;
        set;
    

DataObject 类则变为:

public class DataObject

    [JsonProperty("user_id")]
    public int UserId  get; set; 

    [JsonProperty("detail_level")]
    public DetailLevel DetailLevel  get; set; 

我知道这可能有点晚了,但我认为其他想要使用 JavaScriptSerializer 而不是 DataContractJsonSerializer 的人可能会喜欢它。

【讨论】:

我已将您的代码与 JsonConverter : JavaScriptConverter 等泛型一起使用,因此此类可用于任何类型。【参考方案5】:

创建一个继承自 JavaScriptConverter 的类。然后你必须实现三件事:

方法-

    序列化 反序列化

物业-

    支持的类型

当您需要对序列化和反序列化过程进行更多控制时,可以使用 JavaScriptConverter 类。

JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[]  new MyCustomConverter() );

DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);

Here is a link for further information

【讨论】:

【参考方案6】:

我已经使用了下面的使用 Newtonsoft.Json。创建一个对象:

 public class WorklistSortColumn
  
    [JsonProperty(PropertyName = "field")]
    public string Field  get; set; 

    [JsonProperty(PropertyName = "dir")]
    public string Direction  get; set; 

    [JsonIgnore]
    public string SortOrder  get; set; 
  

现在调用下面的方法序列化为 Json 对象,如下所示。

string sortColumn = JsonConvert.SerializeObject(worklistSortColumn);

【讨论】:

【参考方案7】:

对于那些出于某种原因不想使用Newtonsoft Json.Net 或DataContractJsonSerializer 的人(我想不出任何:)),这是支持DataContract 和@ 的JavaScriptConverter 的实现987654326@到string的转换-

    public class DataContractJavaScriptConverter : JavaScriptConverter
    
        private static readonly List<Type> _supportedTypes = new List<Type>();

        static DataContractJavaScriptConverter()
        
            foreach (Type type in Assembly.GetExecutingAssembly().DefinedTypes)
            
                if (Attribute.IsDefined(type, typeof(DataContractAttribute)))
                
                    _supportedTypes.Add(type);
                
            
        

        private bool ConvertEnumToString = false;

        public DataContractJavaScriptConverter() : this(false)
        
        

        public DataContractJavaScriptConverter(bool convertEnumToString)
        
            ConvertEnumToString = convertEnumToString;
        

        public override IEnumerable<Type> SupportedTypes
        
            get  return _supportedTypes; 
        

        public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
        
            if (Attribute.IsDefined(type, typeof(DataContractAttribute)))
            
                try
                
                    object instance = Activator.CreateInstance(type);

                    IEnumerable<MemberInfo> members = ((IEnumerable<MemberInfo>)type.GetFields())
                        .Concat(type.GetProperties().Where(property => property.CanWrite && property.GetIndexParameters().Length == 0))
                        .Where((member) => Attribute.IsDefined(member, typeof(DataMemberAttribute)));
                    foreach (MemberInfo member in members)
                    
                        DataMemberAttribute attribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute));
                        object value;
                        if (dictionary.TryGetValue(attribute.Name, out value) == false)
                        
                            if (attribute.IsRequired)
                            
                                throw new SerializationException(String.Format("Required DataMember with name 0 not found", attribute.Name));
                            
                            continue;
                        
                        if (member.MemberType == MemberTypes.Field)
                        
                            FieldInfo field = (FieldInfo)member;
                            object fieldValue;
                            if (ConvertEnumToString && field.FieldType.IsEnum)
                            
                                fieldValue = Enum.Parse(field.FieldType, value.ToString());
                            
                            else
                            
                                fieldValue = serializer.ConvertToType(value, field.FieldType);
                            
                            field.SetValue(instance, fieldValue);
                        
                        else if (member.MemberType == MemberTypes.Property)
                        
                            PropertyInfo property = (PropertyInfo)member;
                            object propertyValue;
                            if (ConvertEnumToString && property.PropertyType.IsEnum)
                            
                                propertyValue = Enum.Parse(property.PropertyType, value.ToString());
                            
                            else
                            
                                propertyValue = serializer.ConvertToType(value, property.PropertyType);
                            
                            property.SetValue(instance, propertyValue);
                        
                    
                    return instance;
                
                catch (Exception)
                
                    return null;
                
            
            return null;
        

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
        
            Dictionary<string, object> dictionary = new Dictionary<string, object>();
            if (obj != null && Attribute.IsDefined(obj.GetType(), typeof(DataContractAttribute)))
            
                Type type = obj.GetType();
                IEnumerable<MemberInfo> members = ((IEnumerable<MemberInfo>)type.GetFields())
                    .Concat(type.GetProperties().Where(property => property.CanRead && property.GetIndexParameters().Length == 0))
                    .Where((member) => Attribute.IsDefined(member, typeof(DataMemberAttribute)));
                foreach (MemberInfo member in members)
                
                    DataMemberAttribute attribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute));
                    object value;
                    if (member.MemberType == MemberTypes.Field)
                    
                        FieldInfo field = (FieldInfo)member;
                        if (ConvertEnumToString && field.FieldType.IsEnum)
                        
                            value = field.GetValue(obj).ToString();
                        
                        else
                        
                            value = field.GetValue(obj);
                        
                    
                    else if (member.MemberType == MemberTypes.Property)
                    
                        PropertyInfo property = (PropertyInfo)member;
                        if (ConvertEnumToString && property.PropertyType.IsEnum)
                        
                            value = property.GetValue(obj).ToString();
                        
                        else
                        
                            value = property.GetValue(obj);
                        
                    
                    else
                    
                        continue;
                    
                    if (dictionary.ContainsKey(attribute.Name))
                    
                        throw new SerializationException(String.Format("More than one DataMember found with name 0", attribute.Name));
                    
                    dictionary[attribute.Name] = value;
                
            
            return dictionary;
        
    

注意:这个DataContractJavaScriptConverter 将只处理在它所在的程序集中定义的DataContract 类。如果您想要来自不同程序集的类,请在静态构造函数中相应地修改_supportedTypes 列表。

这可以如下使用-

    JavaScriptSerializer serializer = new JavaScriptSerializer();
    serializer.RegisterConverters(new JavaScriptConverter[]  new DataContractJavaScriptConverter(true) );
    DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);

DataObject 类看起来像这样 -

    using System.Runtime.Serialization;

    [DataContract]
    public class DataObject
    
        [DataMember(Name = "user_id")]
        public int UserId  get; set; 

        [DataMember(Name = "detail_level")]
        public string DetailLevel  get; set; 
    

请注意,此解决方案不处理 DataMember 属性支持的 EmitDefaultValueOrder 属性。

【讨论】:

ASP.NET 在将客户端 json(通过 ajax 到 WebMethod)转换为 .NET 对象时使用JavascriptSerializer,因此像我这样在网站上工作的想要处理客户端数据的用户别无选择使用微软的版本。对于序列化,我们当然可以使用 Newtonsoft,但是反序列化需要这个。来源:referencesource.microsoft.com/#System.Web.Extensions/Script/…【参考方案8】:

我的要求包括:

必须遵守数据合同 必须以服务中收到的格式反序列化日期 必须处理集合 必须以 3.5 为目标 不得添加外部依赖项,尤其是 Newtonsoft(我正在自己创建可分发包) 不得手动反序列化

我最终的解决方案是使用 SimpleJson(https://github.com/facebook-csharp-sdk/simple-json)。

虽然您可以通过 nuget 包安装它,但我在项目中只包含了一个 SimpleJson.cs 文件(使用 MIT 许可证)并引用了它。

我希望这对某人有所帮助。

【讨论】:

以上是关于JavaScriptSerializer.Deserialize - 如何更改字段名称的主要内容,如果未能解决你的问题,请参考以下文章