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

问题描述:

摘要:如何使用JavaScriptSerializer.Deserialize将JSON数据中的字段名称映射到.Net对象的字段名称?JavaScriptSerializer.Deserialize - 如何更改字段名称

加长版:我有以下的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是一个具有“低”作为值之一的枚举。

该测试失败:

[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中进行字段名称映射?它可以完成吗?

+1

我讨厌`JavaScriptSerializer`。 `JwtSecurityTokenHandler`通过静态的`JsonExtensions.Serializer`属性来使用它,这意味着在运行时改变它可能会影响其他代码。不幸的是,这些类中的很多都是这种方式。 :( – NathanAldenSr 2016-07-19 19:42:32

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

+1

伟大的解决方案! 与.Net 4.5似乎工作正常的枚举成员只有普通的[DataMember]声明(不需要[EnumMember]等) – 2015-02-09 12:55:47

+0

什么是你的JsonData?当我这样做,因为你已经写,我得到一个SerializationException,表明序列化程序正在期待一个根元素,就好像它期待XML一样。 我的JSON数据是{“user”:“THEDOMAIN \\ MDS”,“password”:“JJJJ”} – 2016-03-03 20:44:03

Json.NET会做你想做的。它支持读取DataContract/DataMember属性以及它自己的属性名称。还有StringEnumConverter类用于序列化枚举值作为名称而不是数字。

创建一个从JavaScriptConverter继承的类。然后,您必须实现三个方面:

方法 -

  1. 序列化
  2. 反序列化

属性 -

  1. SupportedTypes

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

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

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

Here is a link for further information

通过创建一个自定义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); 

我用用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; } 
} 

我appreicate这个威力有点晚,但认为其他人想要使用JavaScriptSerializer而不是DataContractJsonSerializer可能会感激。

+1

我使用过类似JsonConverter :JavaScriptConverter的代码,因此这个类可以与任何类型一起使用。 – 2016-01-05 14:32:11

我的要求包括:

  • 必须履行的dataContracts
  • 必须反序列化日期在服务接收到的格式
  • 必须处理colelctions
  • 必须针对3.5
  • 不能增加外部依赖关系,特别是不Newtonsoft(我自己创建一个可分发的软件包)
  • must not手动解串

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

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

我希望这可以帮助别人。

对于那些谁不想去Newtonsoft Json.NetDataContractJsonSerializer出于某种原因(我想不出任何的:)),这里是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性质。