74 votes

JavaScriptSerializer.Deserialize - comment modifier le nom des champs ?

Résumé : Comment faire correspondre un nom de champ dans des données JSON à un nom de champ d'un objet .Net en utilisant JavaScriptSerializer.Deserialize ?

Version plus longue : J'ai les données JSON suivantes qui me parviennent d'une API serveur (non codée 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 un enum dont l'une des valeurs est "Low".

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 alertes échouent, car il n'y a pas de données dans ces champs. Si je change les données JSON en

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

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

Comment faire le mappage des noms de champs dans JavaScriptSerializer ? Est-il possible de le faire ?

1 votes

Je déteste JavaScriptSerializer . JwtSecurityTokenHandler l'utilise par le biais de la statique JsonExtensions.Serializer ce qui signifie que sa modification pendant l'exécution pourrait affecter d'autres codes qui s'attendent à ce qu'elle reste inchangée. Beaucoup de ces classes sont ainsi, malheureusement :(

73voto

Anthony Points 2537

J'ai fait un autre essai, en utilisant le DataContractJsonSerializer classe. Ceci 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'un enum à une chaîne de caractères - si vous gardez le type enum en place, le DataContractJsonSerializer s'attend à lire une valeur numérique et échoue. Voir DataContractJsonSerializer et Enums pour plus de détails.

À mon avis, c'est assez médiocre, d'autant plus que JavaScriptSerializer le gère correctement. C'est l'exception que vous obtenez en essayant d'analyser une chaîne de caractères dans un enum :

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 le fait de marquer l'enum de cette façon ne change pas ce comportement :

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

Cela semble également fonctionner dans Silverlight.

2 votes

Excellente solution ! Avec .Net 4.5, cela semble fonctionner correctement pour les membres d'un enum avec une simple déclaration [DataMember] (pas besoin de [EnumMember], etc.).

0 votes

Qu'y a-t-il dans vos JsonData ? Lorsque je procède comme vous l'avez écrit, j'obtiens une SerializationException indiquant que le sérialiseur attend un élément Root, comme s'il attendait du XML. Mes données JSON sont {"user" : "THEDOMAIN \\MDS ", "mot de passe" : "JJJJ"}

20voto

Paul Alexander Points 17611

En créant un JavaScriptConverter vous pouvez associer n'importe quel nom à n'importe quelle propriété. Mais cela nécessite de 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 ça :

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

13voto

James Newton-King Points 13880

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 ses propres attributs pour modifier les noms des propriétés. Il y a aussi la classe StringEnumConverter pour sérialiser les valeurs d'énumération sous forme de nom plutôt que de nombre.

1 votes

Un exemple de code de deux lignes montrant l'utilisation de l'attribut serait agréable à voir dans cette réponse.

11voto

Tom Maher Points 158

Il n'existe pas de prise en charge standard pour renommer les propriétés dans l'application JavaScriptSerializer mais vous pouvez très 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;
    }
}

El DataObject La classe devient alors :

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

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

Je comprends que c'est peut-être un peu tard, mais j'ai pensé que d'autres personnes souhaitant utiliser le site Web de la Commission européenne pourraient le faire. JavaScriptSerializer plutôt que le DataContractJsonSerializer pourrait l'apprécier.

1 votes

J'ai utilisé votre code avec des génériques comme JsonConverter<T> : JavaScriptConverter afin que cette classe puisse être utilisée avec n'importe quel type.

5voto

Dan Appleyard Points 2489

Créer une classe héritée de JavaScriptConverter. Vous devez alors implémenter 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 plus 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X