反序列化-如何更改字段名

摘要 : 如何将 JSON 数据中的字段名映射到。使用 JavaScriptSerializer 时的 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 # 语言中具有命名良好的属性。我不能使用 LINQtoJSON,因为我希望它在 Silverlight 之外工作。看起来 XmlElement 标记没有任何效果。我不知道我从哪里得到的想法,他们是相关的,所有,他们可能不是。

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

103299 次浏览

我又试了一次,用的是 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 和枚举

在我看来,这非常糟糕,特别是当 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 会做你想做的(免责声明: 我是包的作者)。它支持读取 DataContract/DataMember 属性以及它自己的属性来更改属性名。还有一个 StringEnumConverter 类,用于将枚举值序列化为名称而不是数字。

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

方法 -

  1. 连载
  2. 反序列化

物业

  1. 支持类型

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

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


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

这里有一个进一步信息的链接

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

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

我使用了如下的 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);

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可能会欣赏它。

我的要求包括:

  • 必须遵守数据合同
  • 必须以服务中接收的格式反序列化日期
  • 必须处理收藏品
  • 目标3.5
  • 不能添加外部依赖项,特别是 Newtonsoft (我自己正在创建一个可分发的软件包)
  • 不能手工反序列化

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

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

我希望这能帮到别人。

对于那些由于某种原因不想使用 牛顿软件 Json. Net或者 DataContractJsonSerializer的人(我想不出任何原因:) ,这里有一个 JavaScriptConverter的实现,它支持 DataContractenumstring的转换-

    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属性。