JavaScriptSerializer.Deserialize – 如何更改字段名称
简介 :如何使用JavaScriptSerializer.Deserialize将JSON数据中的字段名称映射到.Net对象的字段名称?
更长的版本 :我有以下JSON数据来自我的服务器API(不用.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”作为其中一个值的枚举。
此testing失败:
[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字段名称映射? 它可以完成吗?
我再次尝试使用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; } }
而testing是:
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); }
唯一的缺点是,我不得不从一个枚举更改为一个string的DetailLevel – 如果你保持枚举types的地方,DataContractJsonSerializer期望读取一个数值,并失败。 有关更多详细信息,请参阅DataContractJsonSerializer和Enums 。
在我看来这是相当差,特别是当JavaScriptSerializer正确处理它。 这是你试图parsing一个string到枚举的exception:
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中起作用。
通过创build一个自定义的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 );
Json.NET将做你想要的。 它支持读取DataContract / DataMember属性以及它自己的属性名称。 还有StringEnumConverter类用于序列化枚举值作为名称而不是数字。
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
可能会感激。
创build一个从JavaScriptConverterinheritance的类。 然后你必须执行三件事情:
方法-
- 连载
- 反序列化
属性-
- SupportedTypes
当需要更多地控制序列化和反序列化过程时,可以使用JavaScriptConverter类。
JavaScriptSerializer serializer = new JavaScriptSerializer(); serializer.RegisterConverters(new JavaScriptConverter[] { new MyCustomConverter() }); DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);
这里是一个链接的进一步信息
我已经使用了下面的使用Newtonsoft.Json。 创build一个对象:
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);
对于那些不想去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
只能处理放置在程序集中的DataContract
类。 如果您想从单独的程序_supportedTypes
,请在静态constructror中相应地修改_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; } }
请注意,此解决scheme不处理DataMember
属性支持的EmitDefaultValue
和Order
属性。
我的要求包括:
- 必须尊重dataContracts
- 必须以接收的服务格式反序列化date
- 必须处理好
- 必须以3.5为目标
- 不能添加外部依赖,尤其是不能添加Newtonsoft(我自己创build一个可分发的包)
- 不能手动反序列化
我的解决scheme最后是使用SimpleJson( https://github.com/facebook-csharp-sdk/simple-json )。
虽然你可以通过nuget包来安装,但是我在项目中只包含了一个SimpleJson.cs文件(带有MIT许可证)并引用了它。
我希望这可以帮助别人。