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

74

概要: 当使用JavaScriptSerializer.Deserialize时,如何将JSON数据中的字段名映射到.Net对象的字段名?

详细描述: 我从服务器API(不是在.Net中编码)获取以下JSON数据:

{"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#惯用语中具有良好命名的属性。由于我想让它在Silverlight之外工作,因此无法使用LINQ to JSON。看起来XmlElement标记没有任何作用。我不知道我从哪里得到的想法它们是相关的,它们可能根本不相关。

如何在JavaScriptSerializer中进行字段名称映射?是否可以完成?


1
我讨厌 JavaScriptSerializerJwtSecurityTokenHandler 通过静态的 JsonExtensions.Serializer 属性使用它,这意味着在运行时更改它可能会影响其他期望它不变的代码。很多类都是这样的,不幸的是。 :( - NathanAldenSr
[XmlElement("user_id")] 这个XmlElement不存在,你从哪里得到的?请提供完整的命名空间或导入dll。 - Devmyselz
使用[System.Xml.Serialization.XmlElement("user_id")]命名空间。 - Devmyselz
9个回答

74
我尝试使用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和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
很棒的解决方案!使用 .Net 4.5,对于只有普通 [DataMember] 声明的枚举成员似乎可以正常工作(无需 [EnumMember] 等)。 - Konstantin Salavatov
你的JsonData里面有什么?按照你写的方式执行后,我得到了一个SerializationException异常,提示Serializer期望一个根元素,就好像它期望XML一样。我的JSON数据是{"user": "THEDOMAIN\MDS", "password": "JJJJ"}。 - Matt Smith

20

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

13

Json.NET可以满足您的需求(免责声明:我是该程序包的作者)。它支持读取DataContract / DataMember属性以及自己的属性来更改属性名称。此外,还有StringEnumConverter类可用于将枚举值序列化为名称而不是数字。


1
在这个答案中,展示使用该属性的两行代码示例会很好。 - PhonicUK

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 的其他人可能会很感激。


1
我已经使用了您的代码,其中包括泛型,例如JsonConverter<T>:JavaScriptConverter,因此该类可以与任何类型一起使用。 - Adrian Iftode

5

创建一个继承自 JavaScriptConverter 的类。你必须要实现以下三个内容:

方法-

  1. Serialize
  2. Deserialize

属性-

  1. SupportedTypes

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

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

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

这里有更多信息的链接

(注意:该链接为中文版)

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

3

对于那些不想出于某种原因(我想不出任何理由 :))选择 Newtonsoft Json.NetDataContractJsonSerializer 的人,这里提供了一种支持将 DataContractenum 转换为 stringJavaScriptConverter 实现 -

    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,但反序列化需要使用它。 来源:http://referencesource.microsoft.com/#System.Web.Extensions/Script/Serialization/ObjectConverter.cs,809b4f13600ad28c,references - Tyler StandishMan

0

我的要求包括:

  • 必须遵守数据合同
  • 必须以服务接收到的格式反序列化日期
  • 必须处理集合
  • 必须针对3.5版本
  • 不能添加外部依赖项,尤其是不要使用Newtonsoft(我正在创建一个可分发的软件包)
  • 不能手动反序列化

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

虽然你可以通过NuGet包安装它,但我只在我的项目中包含了那个带有MIT许可证的单个SimpleJson.cs文件,并引用了它。

希望这能帮助到某些人。


0
这是 Tom Maher 答案的通用版本。这将自动注册您的 DataObject 中所有嵌套属性类型到转换器中,以便它们可以使用 JsonPropertyAttribute。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Script.Serialization;
using System.Reflection;
using System.Collections;

public class JavascriptConverterFactory<T> : JavaScriptConverter where T : class
{
    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(T) }.Concat(GetAllNestedTypes(typeof(T)));
        }
    }

    public static JavaScriptSerializer Create()
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RegisterConverters(new[] { new JavascriptConverterFactory<T>() });

        return serializer;
    }

    private static Type[] GetAllNestedTypes(Type type)
    {
        ArrayList types = new ArrayList();
        AddNestedMemberPropertyTypesRecursively(types, type);
        return (Type[])types.ToArray(typeof(Type));
    }

    private static ArrayList AddNestedMemberPropertyTypesRecursively(ArrayList types, Type type)
    {
        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)
        {
            if (member is PropertyInfo)
            {
                PropertyInfo property = (PropertyInfo)member;
                bool isAlreadyInArray = types.Contains(property.PropertyType);

                if (!isAlreadyInArray)
                {
                    types.Add(property.PropertyType);
                    if (property.PropertyType.IsArray)
                    {
                        Type arrayElementType = property.PropertyType.GetElementType();
                        isAlreadyInArray = types.Contains(arrayElementType);
                        if (!isAlreadyInArray)
                        {
                            types.Add(arrayElementType);
                            AddNestedMemberPropertyTypesRecursively(types, arrayElementType);
                        }
                    }
                    AddNestedMemberPropertyTypesRecursively(types, property.PropertyType);
                }
            }
        }

        return types;
    }
}

using System;

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class JsonPropertyAttribute : Attribute
{
    public JsonPropertyAttribute(string name)
    {
        Name = name;
    }

    public string Name
    {
        get;
        set;
    }
}

public class DataObject
{
    [JsonProperty("nested:dataobject")]
    NestedDataObject nestedObject { get; set; }
}

public class NestedDataObject
{
    [JsonProperty("some-other-string")]
    string someOtherString { get; set; }
}

使用方法:

public DataObject GetData(responseContent)
{
    JavaScriptSerializer serializer = JavascriptConverterFactory<DataObject>.Create();
    DataObject deserializedData = serializer.Deserialize<DataObject>(responseContent);
    return deserializedData;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接