JavaScriptSerializer.Deserialize - comment changer le nom des champs

résumé : Comment puis-je mapper un nom de champ dans les données JSON à un nom de champ d'un objet .Net en utilisant JavaScriptSerializer.Désérialiser ?

version longue : j'ai les données JSON suivantes provenant d'une API de serveur (non codées en .Net)

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

j'ai le texte suivant C# objet:

[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 avec "bas" comme l'un des valeur.

cet essai é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 affirmations é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"}

puis il passe. Mais je ne peux pas changer le comportement du serveur, et je veux que les classes de clients aient des propriétés bien nommées dans L'idiome C#. Je ne peux pas utiliser LINQ pour JSON car je veux que ça marche en dehors de Silverlight. On dirait que les étiquettes XmlElement n'ont pas effet. Je ne sais pas où j'ai eu l'idée qu'ils étaient pertinents, ils ne sont probablement pas.

Comment faire pour cartographier les noms de champ dans JavaScriptSerializer? Peut-il être fait?

72
demandé sur Anthony 2009-07-08 23:49:39

8 réponses

j'ai pris un autre essai à elle, en utilisant le DataContractJsonSerializer classe. Cela le résout:

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 le niveau de détail 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 pauvre, surtout que JavaScriptSerializer le gère correctement. C'est l'exception que vous obtenez en essayant d'analyser une chaîne 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 marquage de l'enum comme ceci ne change pas ce comportement:

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

cela semble aussi fonctionner à Silverlight.

72
répondu Anthony 2017-05-23 12:02:26

en créant un personnalisé JavaScriptConverter vous pouvez mapper n'importe quel nom à n'importe quelle propriété. Mais il faut coder à la main la carte, ce qui est loin d'être 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>();
    }
}

alors vous pouvez desérialiser comme ceci:

var serializer = new JavaScriptSerializer();
serialzer.RegisterConverters( new[]{ new DataObjectJavaScriptConverter() } );
var dataObj = serializer.Deserialize<DataObject>( json );
20
répondu Paul Alexander 2010-01-05 04:49:16

Json.NET fera ce que vous voulez. Il prend en charge la lecture des attributs DataContract/DataMember ainsi que les siens pour changer les noms des propriétés. Il y a aussi la classe StringEnumConverter pour sérialiser les valeurs enum comme le nom plutôt que le nombre.

13
répondu James Newton-King 2009-07-11 02:24:41

il n'y a pas de support standard pour renommer les propriétés dans JavaScriptSerializer cependant vous pouvez très facilement ajouter votre propre:

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

j'apprécie que ce soit un peu tard, mais j'ai pensé que d'autres personnes voulant utiliser le JavaScriptSerializer plutôt que le DataContractJsonSerializer pourraient l'apprécier.

11
répondu Tom Maher 2014-07-22 14:12:03

créer une classe héritée de JavaScriptConverter. Vous devez alors mettre en œuvre trois choses:

méthodes -

  1. Sérialiser
  2. Deserialize

biens -

  1. typesde soutien

vous pouvez utiliser la classe JavaScriptConverter quand vous avez besoin de plus de contrôle sur le processus de sérialisation et de desérialisation.

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

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

voici un lien pour plus d'information

5
répondu Dan Appleyard 2009-08-11 16:28:10

j'ai utilisé 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; }
  }

appelle maintenant la méthode ci-dessous pour sérialiser à l'objet Json comme montré ci-dessous.

string sortColumn = JsonConvert.SerializeObject(worklistSortColumn);
4
répondu Vishvanatha Achary 2014-07-17 04:16:23

Pour ceux qui ne veulent pas passer pour des Newtonsoft Json.Net ou DataContractJsonSerializer pour une raison quelconque (je ne peux pas penser à tout :) ), voici une mise en œuvre de JavaScriptConverter 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;
        }
    }

Note: Ce DataContractJavaScriptConverter ne s'applique qu'aux classes DataContract définies dans l'ensemble où elles sont placées. Si vous voulez des classes d'assemblages séparés, modifiez le _supportedTypes inscrire en conséquence dans le constructeur statique.

ce qui 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 supportées par l'attribut DataMember .

1
répondu Advait Purohit 2016-05-06 15:43:07

mes exigences incluaient:

  • doit honorer les dataContracts
  • doit désérialiser les dates dans le format reçu au service
  • doivent manipuler des colelctions
  • doivent cible 3.5
  • ne doit 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 était D'utiliser SimpleJson ( https://github.com/facebook-csharp-sdk/simple-json ).

bien que vous puissiez l'installer via un paquet nuget, Je n'ai inclus que ce simple JSON.fichier cs (avec la licence MIT) dans mon projet et référencé.

j'espère que cela aidera quelqu'un.

0
répondu Ev. 2015-09-21 07:11:32