JavaScriptSerializer.Deserialize - Ändern von Feldnamen

74

Zusammenfassung : Wie ordne ich einen Feldnamen in JSON-Daten einem Feldnamen eines .Net-Objekts zu, wenn JavaScriptSerializer.Deserialize verwendet wird?

Längere Version : Ich habe die folgenden JSON-Daten, die von einer Server-API zu mir kommen (nicht in .Net codiert)

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

Ich habe das folgende C # -Objekt dafür:

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

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

Wobei DetailLevel eine Aufzählung mit "Niedrig" als einem der Werte ist.

Dieser Test schlägt fehl:

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

Und die letzten beiden Asserts schlagen fehl, da in diesen Feldern keine Daten vorhanden sind. Wenn ich die JSON-Daten in ändere

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

Dann geht es vorbei. Ich kann das Verhalten des Servers jedoch nicht ändern und möchte, dass die Clientklassen gut benannte Eigenschaften in der C # -Sprache haben. Ich kann LINQ nicht für JSON verwenden, da ich möchte, dass es außerhalb von Silverlight funktioniert. Es sieht so aus, als hätten die XmlElement-Tags keine Wirkung. Ich weiß nicht, woher ich die Idee habe, dass sie überhaupt relevant sind, wahrscheinlich nicht.

Wie wird die Feldnamenzuordnung in JavaScriptSerializer durchgeführt? Kann man das überhaupt machen?

Anthony
quelle
1
Ich hasse JavaScriptSerializer. JwtSecurityTokenHandlerVerwendet es über die statische JsonExtensions.SerializerEigenschaft. Wenn Sie es also zur Laufzeit ändern, kann dies Auswirkungen auf anderen Code haben, der erwartet, dass es unverändert bleibt. Viele dieser Klassen sind leider so. :(
NathanAldenSr

Antworten:

73

Ich habe es noch einmal mit der DataContractJsonSerializer- Klasse versucht . Dies löst es:

Der Code sieht folgendermaßen aus:

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

Und der Test ist:

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

Der einzige Nachteil ist, dass ich DetailLevel von einer Aufzählung in eine Zeichenfolge ändern musste. Wenn Sie den Aufzählungstyp beibehalten, erwartet der DataContractJsonSerializer das Lesen eines numerischen Werts und schlägt fehl. Siehe DataContractJsonSerializer und Aufzählungen für weitere Details.

Meiner Meinung nach ist dies ziemlich schlecht, zumal JavaScriptSerializer es korrekt handhabt. Dies ist die Ausnahme, bei der Sie versuchen, eine Zeichenfolge in eine Aufzählung zu analysieren:

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

Und wenn Sie die Aufzählung so markieren, ändert sich dieses Verhalten nicht:

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

Dies scheint auch in Silverlight zu funktionieren.

Anthony
quelle
1
Tolle Lösung! Mit .Net 4.5 scheint es gut zu funktionieren für Enum-Mitglieder mit einer einfachen [DataMember] -Deklaration (keine Notwendigkeit für [EnumMember] usw.)
Konstantin Salavatov
Was ist in Ihren JsonData? Wenn ich dies so mache, wie Sie es geschrieben haben, erhalte ich eine SerializationException, die besagt, dass der Serializer ein Root-Element erwartet, als würde er XML erwarten. Meine JSON-Daten sind {"Benutzer": "THEDOMAIN \\ MDS", "Passwort": "JJJJ"}
Matt Smith
20

Durch Erstellen eines benutzerdefinierten JavaScriptConverter können Sie jeder Eigenschaft einen beliebigen Namen zuordnen. Es erfordert jedoch eine Handcodierung der Karte, was nicht ideal ist.

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

Dann können Sie wie folgt deserialisieren:

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

Json.NET macht, was Sie wollen (Haftungsausschluss: Ich bin der Autor des Pakets). Es unterstützt das Lesen von DataContract / DataMember-Attributen sowie von eigenen Attributen, um die Eigenschaftsnamen zu ändern. Es gibt auch die StringEnumConverter-Klasse zum Serialisieren von Enum-Werten als Name und nicht als Nummer.

James Newton-King
quelle
1
Ein zweizeiliges Codebeispiel, das die Verwendung des Attributs zeigt, wäre in dieser Antwort gut zu sehen.
PhonicUK
11

Es gibt keine Standardunterstützung für das Umbenennen von Eigenschaften. JavaScriptSerializerSie können jedoch ganz einfach eigene hinzufügen:

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

Die DataObjectKlasse wird dann:

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

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

Ich schätze, dass dies etwas spät sein könnte, dachte aber, dass andere Leute, die das JavaScriptSerializereher nutzen wollen als das, es DataContractJsonSerializervielleicht zu schätzen wissen.

Tom Maher
quelle
1
Ich habe Ihren Code mit Generika wie JsonConverter <T>: JavaScriptConverter verwendet, damit diese Klasse mit jedem Typ verwendet werden kann.
Adrian Iftode
5

Erstellen Sie eine von JavaScriptConverter geerbte Klasse. Sie müssen dann drei Dinge implementieren:

Methoden-

  1. Serialisieren
  2. Deserialisieren

Eigentum-

  1. Unterstützte Typen

Sie können die JavaScriptConverter-Klasse verwenden, wenn Sie mehr Kontrolle über den Serialisierungs- und Deserialisierungsprozess benötigen.

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

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

Hier ist ein Link für weitere Informationen

Dan Appleyard
quelle
5

Ich habe die Verwendung von Newtonsoft.Json wie folgt verwendet. Erstellen Sie ein Objekt:

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

Rufen Sie nun die folgende Methode auf, um wie unten gezeigt zum Json-Objekt zu serialisieren.

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

Für diejenigen, die nicht gehen wollen Newtonsoft Json.Net oder DataContractJsonSerializeraus irgendeinem Grund (ich nicht irgend :) denken kann), hier ist eine Implementierung , JavaScriptConverterdass Träger DataContractund enumzur stringUmwandlung -

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

Hinweis: Dies behandelt DataContractJavaScriptConverternur DataContractKlassen, die in der Assembly definiert sind, in der sie platziert sind. Wenn Sie Klassen aus separaten Assemblys möchten, ändern Sie die _supportedTypesListe im statischen Konstruktionsfehler entsprechend.

Dies kann wie folgt verwendet werden:

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

Die DataObjectKlasse würde so aussehen -

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

Bitte beachten Sie, dass diese Lösung nicht verarbeitet EmitDefaultValueund OrderEigenschaften von DataMemberAttributen unterstützt werden .

Advait Purohit
quelle
ASP.NET verwendet JavascriptSerializerbeim Konvertieren von Client-JSON (über Ajax in eine WebMethod) in ein .NET-Objekt, sodass Benutzer wie ich, die an einer Website arbeiten und Client-Daten verarbeiten möchten, keine andere Wahl haben, als die Microsoft-Version zu verwenden. Für die Serialisierung können wir natürlich Newtonsoft verwenden, aber die Deserialisierung erfordert dies. Quelle: referencesource.microsoft.com/#System.Web.Extensions/Script/…
Tyler StandishMan
0

Meine Anforderungen umfassten:

  • muss die dataContracts einhalten
  • Daten müssen in dem im Service empfangenen Format deserialisiert werden
  • muss mit Sammlungen umgehen
  • muss auf 3.5 abzielen
  • darf KEINE externe Abhängigkeit hinzufügen, insbesondere nicht Newtonsoft (ich erstelle selbst ein verteilbares Paket)
  • darf nicht von Hand deserialisiert werden

Meine Lösung bestand letztendlich darin, SimpleJson ( https://github.com/facebook-csharp-sdk/simple-json ) zu verwenden.

Obwohl Sie es über ein Nuget-Paket installieren können, habe ich nur diese einzelne SimpleJson.cs-Datei (mit der MIT-Lizenz) in mein Projekt aufgenommen und darauf verwiesen.

Ich hoffe das hilft jemandem.

Ev.
quelle