代码之家  ›  专栏  ›  技术社区  ›  Anthony

JavaScriptSerializer.Deserialize-如何更改字段名

  •  72
  • Anthony  · 技术社区  · 7 年前

    摘要 :使用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到JSON,因为我希望它在Silverlight之外工作。看起来xmlement标记没有任何效果。我不知道我从哪里知道它们是相关的,它们可能不是。

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

    8 回复  |  直到 17 年前
        1
  •  72
  •   Anthony    9 年前

    我又试了一次,用 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。

        2
  •  20
  •   Paul Alexander    16 年前

    通过创建自定义 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
  •  13
  •   entropia    7 年前

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

        4
  •  11
  •   Tom Maher    12 年前

    不支持在中重命名属性 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 可能会很感激。

        5
  •  5
  •   Dan Appleyard    17 年前

    创建从JavaScriptConverter继承的类。然后必须实现三件事:

    方法-

    1. 序列化
    2. 反序列化

    财产-

    1. 支持的类型

    当需要对序列化和反序列化过程进行更多控制时,可以使用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
  •  4
  •   Pankrat    12 年前

    我使用了using 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
  •  1
  •   Advait Purohit    10 年前

    为了那些不想去的人 Newtonsoft Json.Net 或 DataContractJsonSerializer 出于某种原因(我想不出任何原因),下面是 JavaScriptConverter 支持 DataContract 和 enum 到 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 只会处理 数据合同 在其所在的程序集中定义的类。如果要从单独的程序集中获取类,请修改 _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; }
        }
    

    请注意,此解决方案无法处理 EmitDefaultValue 和 Order 支持的属性 DataMember 属性。

        8
  •  0
  •   Ev.    11 年前

    我的要求包括:

    • 必须遵守数据合同
    • 必须以服务中接收的格式反序列化日期
    • 必须处理冲突
    • 必须瞄准3.5
    • 不能添加外部依赖项,尤其是Newtonsoft(我自己正在创建一个可分发的包)
    • 不能手动反序列化

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

    尽管您可以通过nuget包安装它,但我在项目中只包含了那个SimpleJson.cs文件(带有MIT许可证)并引用了它。

    我希望这对某人有帮助。

    推荐文章