2009-07-08 24 views
72

Resumen: ¿Cómo correlao un nombre de campo en datos JSON con un nombre de campo de un objeto .Net cuando uso JavaScriptSerializer.Deserialize?JavaScriptSerializer.Deserialize - cómo cambiar los nombres de los campos

ya la versión: He los siguientes datos JSON venir a mí de una API de servidor (no codificado en .Net)

{"user_id":1234, "detail_level":"low"} 

tengo el siguiente C# objeto para el que:

[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 los dos últimos afirma fallar, 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?

+1

Odio 'JavaScriptSerializer'. 'JwtSecurityTokenHandler' lo usa a través de la propiedad estática' JsonExtensions.Serializer', lo que significa que cambiarlo durante el tiempo de ejecución podría afectar a otro código que espera que no se modifique. Muchas de estas clases son de esa manera, desafortunadamente. :( – NathanAldenSr

Respuesta

72

Traté de probarlo otra vez, usando la clase DataContractJsonSerializer. Esto resuelve que:

El código es el siguiente:

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 mantiene el tipo de enumeración en su lugar, DataContractJsonSerializer espera leer un valor numérico y falla. Ver DataContractJsonSerializer and Enums para más detalles.

En mi opinión, esto es bastante pobre, especialmente porque JavaScriptSerializer lo maneja correctamente. Esta es la excepción que usted consigue intentar 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 marcando a la enumeración como esto no cambia este comportamiento:

[DataContract] 
public enum DetailLevel 
{ 
    [EnumMember(Value = "low")] 
    Low, 
    ... 
} 

Esto también parece funcionar en Silverlight .

+1

¡Gran solución! Con .Net 4.5 parece funcionar bien para los miembros enum con la simple declaración [DataMember] (no es necesario [EnumMember] etc.) –

+0

¿Qué hay en su JsonData? Cuando hago esto como usted lo ha escrito , Obtengo una SerializationException en el sentido de que el serializador espera un elemento raíz, como si esperara XML. Mis datos JSON son {"usuario": "THEDOMAIN \\ MDS", "contraseña": "JJJJ"} –

13

Json.NET hará lo que quiera. 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.

5

Crea una clase heredada de JavaScriptConverter.A continuación, debe aplicar tres cosas:

métodos-

  1. Serialize
  2. Deserialize

Propiedad-

  1. SupportedTypes

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); 

Here is a link for further information

20

Mediante la creación de una costumbre JavaScriptConverter 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>(); 
    } 
} 

A continuación, puede deserializar así:

var serializer = new JavaScriptSerializer(); 
serialzer.RegisterConverters(new[]{ new DataObjectJavaScriptConverter() }); 
var dataObj = serializer.Deserialize<DataObject>(json); 
4

He utilizado el uso de Newtonsoft.Json de la siguiente manera. Cree 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); 
11

No hay soporte estándar para cambiar el nombre de propiedades en JavaScriptSerializer sin embargo se puede añadir fácilmente su propio:

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 Ésta se transforma en:

public class DataObject 
{ 
    [JsonProperty("user_id")] 
    public int UserId { get; set; } 

    [JsonProperty("detail_level")] 
    public DetailLevel DetailLevel { get; set; } 
} 

I appreicate esto podría Llegue un poco tarde, pero pensó que otras personas que quieran usar el JavaScriptSerializer en lugar del DataContractJsonSerializer lo apreciarían.

+1

He usado tu código con genéricos como JsonConverter : JavaScriptConverter, por lo que esta clase se puede usar con cualquier tipo. –

0

Mis requisitos incluyen:

  • debe respetar las DataContracts
  • debe deserializar fechas en el formato recibido en el servicio
  • debe manejar COLELCTIONS
  • debe apuntar a 3,5
  • no debe añadir una externa dependencia, 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í solo ese archivo SimpleJson.cs (con la licencia MIT) en mi proyecto y lo hice referencia.

Espero que esto ayude a alguien.

1

Para aquellos que no quieren ir a por Newtonsoft Json.Net o DataContractJsonSerializer por alguna razón (no puedo pensar en ninguna :)), aquí es una implementación de JavaScriptConverter que soporta DataContract y enum a string conversión -

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 DataContract definidas en el conjunto donde se coloca. Si desea clases de ensamblajes separados, modifique la lista _supportedTypes en consecuencia en el constructor estático.

Esto se puede utilizar 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 EmitDefaultValue y Order propiedades admitidas por DataMember atributo .

+0

ASP.NET utiliza 'JavascriptSerializer' al convertir el cliente json (a través de ajax en un WebMethod) en un objeto .NET, por lo que los usuarios como yo, trabajando en un sitio web que desea manejar los datos del cliente no tengo más remedio que utilizar los vers de Microsoft ion. Para la serialización, podemos, por supuesto, usar Newtonsoft, pero la deserialización lo requiere. Fuente: http://referencesource.microsoft.com/#System.Web.Extensions/Script/Serialization/ObjectConverter.cs,809b4f13600ad28c,references –

Cuestiones relacionadas