web-dev-qa-db-fra.com

JavaScriptSerializer.Deserialize - Comment modifier les noms de champs

Résumé: Comment mapper un nom de champ dans les données JSON avec le nom de champ d'un objet .Net lors de l'utilisation de JavaScriptSerializer.Deserialize?

Version plus longue: J'ai les données JSON suivantes qui me parviennent d'une API de serveur (Non codé en .Net)

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

J'ai l'objet C # suivant pour cela:

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

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

Où DetailLevel est une énumération avec "Low" comme l'une des valeurs.

Ce test échoue:

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

Et les deux dernières assertions échouent, car il n'y a pas de données dans ces champs. Si je modifie les données JSON en

 {"userid":1234, "detaillevel":"low"}

Puis ça passe. Mais je ne peux pas changer le comportement du serveur et je veux que les classes client aient des propriétés bien nommées dans l'idiome C #. Je ne peux pas utiliser LINQ to JSON car je souhaite que cela fonctionne en dehors de Silverlight. Il semble que les balises XmlElement n'aient aucun effet. Je ne sais pas où j'ai eu l'idée qu'ils étaient pertinents, ils ne le sont probablement pas.

Comment faites-vous le mappage des noms de champs dans JavaScriptSerializer? Cela peut-il être fait du tout?

72
Anthony

J'ai essayé à nouveau, en utilisant la classe DataContractJsonSerializer . Cela résout le problème:

Le code ressemble à ceci:

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

Et le test est:

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

Le seul inconvénient est que j'ai dû changer DetailLevel d'une énumération à une chaîne. Si vous conservez le type enum en place, DataContractJsonSerializer s'attend à lire une valeur numérique et échoue. Voir DataContractJsonSerializer and Enums pour plus de détails.

A mon avis, c'est assez médiocre, d'autant plus que JavaScriptSerializer le gère correctement. C'est l'exception que vous essayez d'analyser une chaîne dans une énumération:

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

Et marquer l’énum comme ceci ne change pas ce comportement:

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

Cela semble aussi fonctionner dans Silverlight.

72
Anthony

En créant un JavaScriptConverter personnalisé , vous pouvez associer n'importe quel nom à n'importe quelle propriété. Mais il faut coder manuellement la carte, ce qui n’est pas idéal.

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

Ensuite, vous pouvez désérialiser comme ceci:

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

Json.NET fera ce que vous voulez (disclaimer: je suis l'auteur du paquet). Il prend en charge la lecture des attributs DataContract/DataMember ainsi que le sien pour modifier les noms de propriété. Il existe également la classe StringEnumConverter pour la sérialisation des valeurs enum sous le nom plutôt que le nombre.

13
James Newton-King

Il n'y a pas de support standard pour renommer des propriétés dans JavaScriptSerializer mais vous pouvez assez facilement ajouter les vôtres:

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 classe DataObject devient alors:

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

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

Je pense que cela pourrait être un peu tard, mais je pensais que d'autres personnes souhaitant utiliser le JavaScriptSerializer plutôt que le DataContractJsonSerializer pourraient l'apprécier.

11
Tom Maher

Créez une classe héritée de JavaScriptConverter. Vous devez ensuite mettre en œuvre trois choses:

Méthodes -

  1. Sérialiser
  2. Désérialiser

Propriété -

  1. Types supportés

Vous pouvez utiliser la classe JavaScriptConverter lorsque vous avez besoin de davantage de contrôle sur le processus de sérialisation et de désérialisation.

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

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

Voici un lien pour plus d'informations

5
Dan Appleyard

J'ai utilisé le utilisant Newtonsoft.Json comme ci-dessous. Créer un objet:

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

Appelez maintenant la méthode ci-dessous pour sérialiser un objet Json comme indiqué ci-dessous.

string sortColumn = JsonConvert.SerializeObject(worklistSortColumn);
4
Vishvanatha Achary

Pour ceux qui ne veulent pas aller pour Newtonsoft Json.Net ou DataContractJsonSerializer pour une raison quelconque (je ne vois aucun :)), voici une implémentation de JavaScriptConverter qui supporte DataContract et enum à string conversion -

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

Remarque: Ce DataContractJavaScriptConverter ne gérera que les classes DataContract définies dans l’Assemblée où il est placé. Si vous voulez des classes d’assemblages séparés, modifiez le _supportedTypes liste en conséquence dans le constructeur statique.

Ceci peut être utilisé comme suit -

    JavaScriptSerializer serializer = new JavaScriptSerializer();
    serializer.RegisterConverters(new JavaScriptConverter[] { new DataContractJavaScriptConverter(true) });
    DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);

La classe DataObject ressemblerait à ceci -

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

Veuillez noter que cette solution ne gère pas les propriétés EmitDefaultValue et Order gérées par l'attribut DataMember.

1
Advait Purohit

Mes exigences comprenaient:

  • doit honorer les dataContracts
  • doit désérialiser les dates au format reçu en service
  • doit gérer les colelctions
  • doit cibler 3,5
  • nE PAS ajouter de dépendance externe, surtout pas Newtonsoft (je crée moi-même un paquet distribuable)
  • ne doit pas être désérialisé à la main

Ma solution à la fin a été d'utiliser SimpleJson ( https://github.com/facebook-csharp-sdk/simple-json ).

Bien que vous puissiez l'installer via un paquet de nugets, j'ai uniquement inclus ce fichier SimpleJson.cs (avec la licence MIT)] dans mon projet et l'ai référencé.

J'espère que ça aidera quelqu'un.

0
Ev.