JavasCriptserializer.Desialize – Veldnamen wijzigen

Summary : Hoe kan ik een veldnaam in JSON-gegevens in kaart brengen naar een veldnaam van een .NET-object bij het gebruik van Javascriptserizer.Desialize?

Langere versie : Ik heb de volgende JSON-gegevens die naar me toe komen van een server-API (niet gecodeerd in .NET)

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

Ik heb het volgende C # -object voor:

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

Waar DetailLevel een ENUM is met “Laag” als een van de waarden.

Deze test mislukt:

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

en de laatste twee beweringen mislukken, omdat er geen gegevens in die velden zijn. Als ik de JSON-gegevens wijzigen in

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

Dan passeert het. Maar ik kan het gedrag van de server niet veranderen, en ik wil dat de clientclasses wel-genoemde eigenschappen in het C # Idioom hebben. Ik kan LinQ niet gebruiken aan JSON sinds ik het wil om buiten te werken buiten Silverlight. Het lijkt erop dat de Xmlelement-tags geen effect hebben. Ik weet niet waar ik het idee kreeg dat ze helemaal relevant waren, ze zijn waarschijnlijk niet.

Hoe gaat u veldnaammapping in JavasCriptserizer? Kan het helemaal worden gedaan?


Antwoord 1, Autoriteit 100%

Ik heb er nog een keer geprobeerd, met behulp van de DATACONTRACTEJSONSERNIER klasse. Dit lost het op:

De code ziet eruit als volgt:

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

en de test is:

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

Het enige nadeel is dat ik DetailLevel van een ENUM moet wijzigen naar een string – als u het ENUM-type op zijn plaats houdt, verwacht het datacontractenvlieger een numerieke waarde en mislukt. Zie DATACONTRACTJSONSERIERIER EN ENUMS voor meer informatie.

Naar mijn mening is dit vrij slecht, vooral omdat JavasCriptsializer het correct verwerkt. Dit is de uitzondering die u probeert een string te ontleden in een 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

en markering van de ENUM zoals dit verandert dit gedrag niet:

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

Dit lijkt ook in Silverlight te werken.


Antwoord 2, Autoriteit 27%

Door een aangepaste javascriptconverter U kunt elke naam in kaart brengen aan elke eigenschap. Maar het vereist een hand die de kaart coderen, wat minder is dan ideaal.

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

Dan kunt u zo deializeren als:

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

Antwoord 3, Autoriteit 18%

json.net zal doen wat u wilt (Disclaimer: I ‘ m de auteur van het pakket). Het ondersteunt het lezen van datacontract / datamember-attributen en zijn eigen om de eigendomsnamen te veranderen. Ook is er de klassen van strikenumconverter voor het serialiseren van ENUM-waarden als de naam in plaats van het nummer.


Antwoord 4, Autoriteit 15%

Er is geen standaardondersteuning voor het hernoemen van eigenschappen in JavaScriptSerializer, maar je kunt eenvoudig je eigen toevoegen:

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

De klasse DataObjectwordt dan:

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

Ik begrijp dat dit misschien een beetje laat is, maar ik dacht dat andere mensen die de JavaScriptSerializerwillen gebruiken in plaats van de DataContractJsonSerializerdit misschien op prijs zouden stellen.


Antwoord 5, autoriteit 7%

Maak een klasse die is overgenomen van JavaScriptConverter. U moet dan drie dingen implementeren:

Methoden-

  1. Serialiseren
  2. Deserialiseren

Eigendom-

  1. SupportedTypes

Je kunt de JavaScriptConverter-klasse gebruiken als je meer controle nodig hebt over het serialisatie- en deserialisatieproces.

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

Hier is een link voor meer informatie


Antwoord 6, autoriteit 7%

Ik heb het gebruik van Newtonsoft.Json gebruikt zoals hieronder. Maak een object:

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

Roep nu de onderstaande methode aan om te serialiseren naar het Json-object, zoals hieronder wordt weergegeven.

string sortColumn = JsonConvert.SerializeObject(worklistSortColumn);

Antwoord 7, autoriteit 4%

Voor degenen die niet willen gaan voor Newtonsoft Json.Netof DataContractJsonSerializerom de een of andere reden (ik kan er geen bedenken 🙂 ), is hier een implementatie van JavaScriptConverterdie DataContracten enumondersteunt naar stringconversie –

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

Opmerking: Deze DataContractJavaScriptConverterverwerkt alleen DataContract-klassen die zijn gedefinieerd in de assembly waarin deze is geplaatst. Als u klassen van afzonderlijke assemblages wilt, pas dan de _supportedTypeslijst dienovereenkomstig aan in de statische constructror.

Dit kan als volgt worden gebruikt –

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

De klasse DataObjectziet er als volgt uit –

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

Houd er rekening mee dat deze oplossing de eigenschappen EmitDefaultValueen Orderniet verwerkt die worden ondersteund door het kenmerk DataMember.


Antwoord 8

Mijn vereisten omvatten:

  • moet de dataContracts nakomen
  • moet datums deserialiseren in het formaat ontvangen in service
  • moet verzamelingen afhandelen
  • moet 3,5 targeten
  • mag GEEN externe afhankelijkheid toevoegen, vooral niet Newtonsoft (ik maak zelf een distribueerbaar pakket)
  • mag niet met de hand worden gedeserialiseerd

Mijn oplossing was uiteindelijk om SimpleJson te gebruiken(https://github.com/ facebook-csharp-sdk/simple-json).

Hoewel je het kunt installeren via een nuget-pakket, heb ik alleen dat ene SimpleJson.cs-bestand (met de MIT-licentie) in mijn project opgenomen en ernaar verwezen.

Ik hoop dat dit iemand helpt.

Other episodes