c# - obtener - JavaScriptSerializer.Deserialize-cómo cambiar los nombres de los campos
rename column dataset c# (8)
Resumen : ¿Cómo correlao un nombre de campo en datos JSON con un nombre de campo de un objeto .Net cuando uso JavaScriptSerializer.Deserialize?
Versión más larga : recibo los siguientes datos JSON de una API de servidor (No codificado en .Net)
{"user_id":1234, "detail_level":"low"}
Tengo el siguiente objeto C # para él:
[Serializable]
public class DataObject
{
[XmlElement("user_id")]
public int UserId { get; set; }
[XmlElement("detail_level")]
public DetailLevel DetailLevel { get; set; }
}
Donde DetailLevel es una enumeración con "Bajo" como uno de los valores.
Esta prueba falla:
[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);
}
Y las dos últimas afirmaciones fallan, ya que no hay datos en esos campos. Si cambio los datos JSON a
{"userid":1234, "detaillevel":"low"}
Luego pasa. Pero no puedo cambiar el comportamiento del servidor, y quiero que las clases del cliente tengan propiedades bien nombradas en la expresión C #. No puedo usar LINQ to JSON porque quiero que funcione fuera de Silverlight. Parece que las etiquetas XmlElement no tienen ningún efecto. No sé de dónde saqué la idea de que eran relevantes en absoluto, probablemente no.
¿Cómo se hace la asignación de nombre de campo en JavaScriptSerializer? ¿Se puede hacer?
Al crear un JavaScriptConverter personalizado puede asignar cualquier nombre a cualquier propiedad. Pero requiere una codificación manual del mapa, que es menos que ideal.
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>();
}
}
Entonces puedes deserializar así:
var serializer = new JavaScriptSerializer();
serialzer.RegisterConverters( new[]{ new DataObjectJavaScriptConverter() } );
var dataObj = serializer.Deserialize<DataObject>( json );
Crea una clase heredada de JavaScriptConverter. A continuación, debe implementar tres cosas:
Métodos-
- Publicar por fascículos
- Deserializar
Propiedad-
- Tipos soportados
Puede usar la clase JavaScriptConverter cuando necesite más control sobre el proceso de serialización y deserialización.
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new MyCustomConverter() });
DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);
He usado el uso de Newtonsoft.Json como a continuación. Crea un objeto:
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; }
}
Ahora llame al siguiente método para serializar al objeto Json como se muestra a continuación.
string sortColumn = JsonConvert.SerializeObject(worklistSortColumn);
Intenté otra vez, usando la clase DataContractJsonSerializer . Esto lo resuelve:
El código se ve así:
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; }
}
Y la prueba es:
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);
}
El único inconveniente es que tuve que cambiar DetailLevel de una enumeración a una cadena; si mantienes el tipo de enumeración en su lugar, el DataContractJsonSerializer espera leer un valor numérico y falla. Ver DataContractJsonSerializer y Enumerados para más detalles.
En mi opinión, esto es bastante pobre, especialmente porque JavaScriptSerializer lo maneja correctamente. Esta es la excepción que obtiene tratando de analizar una cadena en una enumeración:
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
Y marcar la enumeración de esta manera no cambia este comportamiento:
[DataContract]
public enum DetailLevel
{
[EnumMember(Value = "low")]
Low,
...
}
Esto también parece funcionar en Silverlight.
Mis requisitos incluyen:
- debe respetar los datosContratos
- debe deserializar las fechas en el formato recibido en servicio
- debe manejar colelctions
- debe apuntar 3.5
- NO debe agregar una dependencia externa, especialmente no Newtonsoft (estoy creando un paquete distribuible)
- no debe ser deserializado a mano
Mi solución al final fue usar SimpleJson ( https://github.com/facebook-csharp-sdk/simple-json ).
Aunque puedes instalarlo a través de un paquete nuget, incluí ese único archivo SimpleJson.cs (con la licencia MIT) en mi proyecto y lo hice referencia.
Espero que esto ayude a alguien.
No hay soporte estándar para cambiar el nombre de las propiedades en JavaScriptSerializer
sin embargo, puede agregar fácilmente las suyas propias:
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;
}
}
La clase DataObject
se convierte en:
public class DataObject
{
[JsonProperty("user_id")]
public int UserId { get; set; }
[JsonProperty("detail_level")]
public DetailLevel DetailLevel { get; set; }
}
Considero que esto podría ser un poco tarde, pero pensé que otras personas que querían utilizar el JavaScriptSerializer
lugar del DataContractJsonSerializer
podrían apreciarlo.
Para aquellos que no quieren ir a Newtonsoft Json.Net o DataContractJsonSerializer
por alguna razón (no se me ocurre :)), aquí hay una implementación de JavaScriptConverter
que admite DataContract
y enum
para la conversión de 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;
}
}
Nota: Este DataContractJavaScriptConverter
solo manejará las clases de DataContract
definidas en el ensamblaje donde se coloca. Si desea clases de ensamblajes separados, modifique la lista _supportedTypes
en consecuencia en el constructor estático.
Esto se puede usar de la siguiente manera:
JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new DataContractJavaScriptConverter(true) });
DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);
La clase DataObject
se vería así:
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; }
}
Tenga en cuenta que esta solución no maneja las propiedades EmitDefaultValue
y Order
compatibles con el atributo DataMember
.
Json.NET hará lo que quieras. Admite la lectura de los atributos DataContract / DataMember, así como los propios, para cambiar los nombres de las propiedades. También existe la clase StringEnumConverter para serializar valores enum como el nombre en lugar del número.