JavaScriptSerializer.Deserialize - як змінити імена полів


74

Короткий зміст : Як зіставити ім’я поля в даних 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 - це перелік з одним із значень "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. Схоже, теги XmlElement не мають ефекту. Я не знаю, звідки я взяв ідею, що вони взагалі були доречні, можливо, ні.

Як виконується зіставлення імен полів у JavaScriptSerializer? Чи можна це зробити взагалі?


1
Я ненавиджу JavaScriptSerializer. JwtSecurityTokenHandlerвикористовує його через статичну JsonExtensions.Serializerвластивість, що означає, що його зміна під час виконання може вплинути на інший код, який очікує, що він не зміниться. На жаль, багато з цих класів саме такі. :(
NathanAldenSr

Відповіді:


73

Я зробив ще одну спробу, використовуючи клас 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.


1
Чудове рішення! Здається, що .Net 4.5 працює нормально для учасників перерахування з простою декларацією [DataMember] (не потрібно [EnumMember] тощо)
Костянтин Салалатов,

Що у ваших JsonData? Коли я роблю це, як ви писали, я отримую SerializationException, згідно з яким Serializer очікує кореневий елемент, ніби він очікує XML. Мої дані JSON - це "user": "THEDOMAIN \\ MDS", "password": "JJJJ"}
Метт Сміт

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

11

Немає стандартної підтримки перейменування властивостей, 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, тому цей клас можна використовувати з будь-яким типом.
Адріан Іфтоде

5

Створіть клас, успадкований від JavaScriptConverter. Потім потрібно реалізувати три речі:

Методи-

  1. Серіалізувати
  2. Десериалізувати

Власність-

  1. Підтримувані типи

Ви можете використовувати клас JavaScriptConverter, коли вам потрібен більше контролю над процесом серіалізації та десеріалізації.

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

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

Ось посилання для отримання додаткової інформації


5

Я використовував використання 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);

2

Для тих , хто не хоче йти на 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 список у статичному конструкторі.

Це можна використовувати наступним чином -

    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атрибутом.


ASP JavascriptSerializer. Для серіалізації ми, звичайно, можемо використовувати Newtonsoft, але десеріалізація вимагає цього. Джерело: referenceource.microsoft.com/#System.Web.Extensions/Script/…
Тайлер Стендіш,

0

Мої вимоги включали:

  • повинен дотримуватися dataContracts
  • повинен десеріалізувати дати у форматі, отриманому в службі
  • повинен обробляти паралелі
  • повинен бути націлений на 3.5
  • НЕ повинен додавати зовнішню залежність, особливо не Newtonsoft (я сам створюю розповсюджуваний пакет)
  • не можна десериалізувати вручну

Зрештою, моїм рішенням було використання SimpleJson ( https://github.com/facebook-csharp-sdk/simple-json ).

Незважаючи на те, що ви можете встановити його за допомогою nuget-пакету, я включив у цей проект лише один файл SimpleJson.cs (з ліцензією MIT) і посилався на нього.

Сподіваюся, це комусь допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.