JavaScriptSerializer.Deserialize - как изменить имена полей
Резюме. Как сопоставить имя поля в данных JSON с именем поля объекта .Net при использовании JavaScriptSerializer.Deserialize?
Более длинная версия. У меня есть следующие данные JSON, поступающие ко мне из API-интерфейса сервера (не закодированного в .Net)
{"user_id":1234, "detail_level":"low"}
У меня есть следующий объект С# для него:
[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"}
Затем он проходит. Но я не могу изменить поведение сервера, и я хочу, чтобы клиентские классы имели хорошо названные свойства в идиоме С#. Я не могу использовать LINQ для JSON, так как хочу, чтобы он работал вне Silverlight. Похоже, что теги XmlElement не имеют никакого эффекта. Я не знаю, где у меня идея, что они актуальны, они, вероятно, не так.
Как вы выполняете сопоставление имени поля в JavaScriptSerializer? Можно ли это сделать вообще?
Ответы
Ответ 1
Я попробовал еще раз, используя класс 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 из enum на строку - если вы сохраните тип перечисления на месте, 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
Создав собственный 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 );
Ответ 3
Json.NET будет делать то, что вы хотите (отказ от ответственности: я автор пакета). Он поддерживает чтение атрибутов DataContract/DataMember, а также свои собственные для изменения имен свойств. Также есть класс StringEnumConverter для сериализации значений перечисления как имени, а не числа.
Ответ 4
Нет стандартной поддержки для переименования свойств в 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
, могут это оценить.
Ответ 5
Создайте класс, унаследованный от JavaScriptConverter. Затем вы должны реализовать три вещи:
Методы -
Свойство -
Вы можете использовать класс JavaScriptConverter, когда вам нужно больше контролировать процесс сериализации и десериализации.
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new MyCustomConverter() });
DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);
Вот ссылка для дополнительной информации
Ответ 6
Я использовал использование 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);
Ответ 7
Для тех, кто по какой-то причине не хочет Newtonsoft Json.Net или DataContractJsonSerializer
(я не могу придумать any:)), вот реализация 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
.
Ответ 8
Мои требования включали:
- должен соблюдать dataContracts
- должен десериализовать даты в формате, полученном в сервисе
- должен обрабатывать colelctions
- должен соответствовать 3.5
- НЕ добавить внешнюю зависимость, особенно не Newtonsoft (я сам создаю дистрибутивный пакет)
- не следует десериализовать вручную
Моим решением в конце было использовать SimpleJson (https://github.com/facebook-csharp-sdk/simple-json).
Хотя вы можете установить его через пакет nuget, я включил только этот единственный файл SimpleJson.cs(с лицензией MIT) в свой проект и ссылался на него.
Я надеюсь, что это поможет кому-то.