Eine Eigenschaft deserialisieren, aber nicht mit json.net serialisieren

122

Wir haben einige Konfigurationsdateien, die durch Serialisieren von C # -Objekten mit Json.net generiert wurden.

Wir möchten eine Eigenschaft der serialisierten Klasse von einer einfachen Enum-Eigenschaft in eine Klasseneigenschaft migrieren.

Eine einfache Möglichkeit, dies zu tun, besteht darin, die alte enum-Eigenschaft in der Klasse zu belassen und Json.net zu veranlassen, diese Eigenschaft beim Laden der Konfiguration zu lesen, sie jedoch beim nächsten Serialisieren des Objekts nicht erneut zu speichern. Wir werden die neue Klasse separat aus der alten Aufzählung generieren.

Gibt es eine einfache Möglichkeit, eine Eigenschaft eines C # -Objekts zu markieren (z. B. mit Attributen), sodass Json.net sie NUR beim Serialisieren ignoriert, beim Deserialisieren jedoch beachtet?

Will Dean
quelle
Was ist mit einem benutzerdefinierten Konverter: Sie können ihn als Attribut für Ihre Eigenschaft verwenden, ReadJson und WriteJson mit unterschiedlichen Verhaltensweisen überschreiben, nein? Beispiel (nicht genau das, was Sie brauchen, aber ...) weblogs.asp.net/thangchung/archive/2010/08/26/…
Raphaël Althaus
OnDeserialized Attribut kann eine Umgehung für Sie sein
Estefany Velez
Sollte das nicht mit dem Attribut "[JsonIgnore]" möglich sein? james.newtonking.com/archive/2009/10/23/…
Juri
Können Sie erweitern, wie dies gemäß dem letzten Absatz des q nur in eine Richtung verwendet werden kann?
Will Dean
Es ist möglich, [JsonIgnore] in Kombination mit einem sekundären privaten Setter zu verwenden, der mit einem [JsonProperty] -Attribut versehen ist. Es gibt auch einige andere einfache Lösungen. Ich habe eine detaillierte Beschreibung hinzugefügt.
Brian Rogers

Antworten:

131

Es gibt tatsächlich mehrere ziemlich einfache Ansätze, mit denen Sie das gewünschte Ergebnis erzielen können.

Nehmen wir zum Beispiel an, Sie haben Ihre Klassen derzeit so definiert:

class Config
{
    public Fizz ObsoleteSetting { get; set; }
    public Bang ReplacementSetting { get; set; }
}

enum Fizz { Alpha, Beta, Gamma }

class Bang
{
    public string Value { get; set; }
}

Und du willst das machen:

string json = @"{ ""ObsoleteSetting"" : ""Gamma"" }";

// deserialize
Config config = JsonConvert.DeserializeObject<Config>(json);

// migrate
config.ReplacementSetting = 
    new Bang { Value = config.ObsoleteSetting.ToString() };

// serialize
json = JsonConvert.SerializeObject(config);
Console.WriteLine(json);

Um dies zu bekommen:

{"ReplacementSetting":{"Value":"Gamma"}}

Ansatz 1: Fügen Sie eine ShouldSerialize-Methode hinzu

Json.NET kann Eigenschaften bedingt serialisieren, indem nach entsprechenden ShouldSerializeMethoden in der Klasse gesucht wird .

Um diese Funktion zu verwenden, fügen Sie ShouldSerializeBlah()Ihrer Klasse eine boolesche Methode hinzu, die Blahdurch den Namen der Eigenschaft ersetzt wird, die Sie nicht serialisieren möchten. Lassen Sie die Implementierung dieser Methode immer zurückkehren false.

class Config
{
    public Fizz ObsoleteSetting { get; set; }

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Hinweis: Wenn Ihnen dieser Ansatz gefällt, Sie aber die öffentliche Oberfläche Ihrer Klasse nicht durch die Einführung einer ShouldSerializeMethode verwirren möchten , können Sie eine verwenden IContractResolver, um dasselbe programmgesteuert auszuführen. Siehe Serielle Serialisierung von Eigenschaften in der Dokumentation.

Ansatz 2: Bearbeiten Sie den JSON mit JObjects

Anstatt JsonConvert.SerializeObjectdie Serialisierung zu verwenden, laden Sie das Konfigurationsobjekt in a JObjectund entfernen Sie dann einfach die unerwünschte Eigenschaft aus dem JSON, bevor Sie sie ausschreiben. Es sind nur ein paar zusätzliche Codezeilen.

JObject jo = JObject.FromObject(config);

// remove the "ObsoleteSetting" JProperty from its parent
jo["ObsoleteSetting"].Parent.Remove();

json = jo.ToString();

Ansatz 3: Clevere (ab) Verwendung von Attributen

  1. Wenden Sie ein [JsonIgnore]Attribut auf die Eigenschaft an, die nicht serialisiert werden soll.
  2. Fügen Sie der Klasse einen alternativen Setter für private Eigenschaften mit demselben Typ wie die ursprüngliche Eigenschaft hinzu. Stellen Sie bei der Implementierung dieser Eigenschaft die ursprüngliche Eigenschaft ein.
  3. Wenden Sie ein [JsonProperty]Attribut auf den alternativen Setter an und geben Sie ihm denselben JSON-Namen wie die ursprüngliche Eigenschaft.

Hier ist die überarbeitete ConfigKlasse:

class Config
{
    [JsonIgnore]
    public Fizz ObsoleteSetting { get; set; }

    [JsonProperty("ObsoleteSetting")]
    private Fizz ObsoleteSettingAlternateSetter
    {
        // get is intentionally omitted here
        set { ObsoleteSetting = value; }
    }

    public Bang ReplacementSetting { get; set; }
}
Brian Rogers
quelle
7
Wir haben es in unserem Projekt gelöst (das einen internen integrationsspezifischen Supersatz des Basismodells verwendet, bei dem keine der Eigenschaften der Oberklasse serialisiert werden sollte), indem wir die get-Eigenschaften auf intern gesetzt haben. Mit öffentlichen Setzern konnte Web Api die Eigenschaften festlegen, konnte sie jedoch nicht serialisieren.
Daniel Saidi
7
In Verbindung mit der Verwendung JsonPropertyAttributevon können Sie ab C # 6.0 das nameofSchlüsselwort anstelle von "magischen Zeichenfolgen" verwenden. Dies macht das Refactoring viel einfacher und kinderleicht. Wenn Sie das Umbenennen von Ereignissen verpassen, werden Sie vom Compiler trotzdem gewarnt. In @ Brians Beispiel wäre die Verwendung wie folgt:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James
1
Es ist eine schlechte Idee, nameof () in JsonProperty-Deklarationen zu verwenden, insbesondere in diesem Legacy-Szenario. Der JSON stellt einen externen (und hoffentlich ewigen) Vertrag mit einer anderen Schnittstelle dar, und Sie möchten den Namen der JSON-Eigenschaft definitiv NICHT ändern, wenn Sie einen Refactor durchführen. Sie würden die Kompatibilität aller vorhandenen JSON-Dateien und -Komponenten beeinträchtigen, die JSON in diesem Format generieren. In der Tat ist es besser, wenn Sie JsonProperty (…) mit dem vollständigen Namen auf jede serialisierte Eigenschaft setzen, um sicherzustellen, dass sie sich nicht ändern, wenn Sie die Eigenschaft später umbenennen.
Munition
34

Für jede Situation, in der es akzeptabel ist, dass Ihre Nur-Deserialisierungseigenschaft als intern markiert wird, gibt es eine bemerkenswert einfache Lösung, die überhaupt nicht von Attributen abhängt. Markieren Sie die Eigenschaft einfach als intern get, aber public set:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Dies führt zu einer korrekten Deserialisierung unter Verwendung der Standardeinstellungen / Resolver / etc., Aber die Eigenschaft wird von der serialisierten Ausgabe entfernt.

Iucounu
quelle
Einfache und doch clevere Lösung.
Hbulens
Beachten Sie, dass die Eigenschaft auch vom Validierungsmodul ignoriert wird. (Sie können es also nicht mehr als [Erforderlich] für die Deserialisierung markieren, da dies auf einer öffentlichen getMethode beruht .)
Martin Hansen
2
Dies funktioniert weder mit internalnoch private. Es wird immer serialisiert.
Paul
Das hat bei mir nicht funktioniert. Beim Deserialisieren wurde eine Eigenschaft nicht gefunden.
Drew Sumido
30

Ich mag es, bei Attributen zu bleiben. Hier ist die Methode, die ich verwende, wenn ich eine Eigenschaft deserialisieren muss, aber nicht serialisieren muss oder umgekehrt.

SCHRITT 1 - Erstellen Sie das benutzerdefinierte Attribut

public class JsonIgnoreSerializationAttribute : Attribute { }

SCHRITT 2 - Erstellen Sie einen benutzerdefinierten Vertrags-Reslover

class JsonPropertiesResolver : DefaultContractResolver
{
    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        //Return properties that do NOT have the JsonIgnoreSerializationAttribute
        return objectType.GetProperties()
                         .Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute)))
                         .ToList<MemberInfo>();
    }
}

SCHRITT 3 - Fügen Sie ein Attribut hinzu, bei dem keine Serialisierung erforderlich ist, die Deserialisierung jedoch

    [JsonIgnoreSerialization]
    public string Prop1 { get; set; } //Will be skipped when serialized

    [JsonIgnoreSerialization]
    public string Prop2 { get; set; } //Also will be skipped when serialized

    public string Prop3 { get; set; } //Will not be skipped when serialized

SCHRITT 4 - Verwenden Sie es

var sweet = JsonConvert.SerializeObject(myObj, new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() });

Hoffe das hilft! Es ist auch erwähnenswert, dass dies auch die Eigenschaften ignoriert, wenn eine Deserialisierung stattfindet. Wenn ich derserialisiere, verwende ich den Konverter nur auf herkömmliche Weise.

JsonConvert.DeserializeObject<MyType>(myString);
Jraco11
quelle
Vielen Dank für diese hilfreiche Implementierung. Gibt es eine Möglichkeit, die Basisimplementierung zu erweitern, GetSerializableMembersanstatt sie vollständig zu überschreiben?
Alain
4
return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Alain
Ich bin mir nicht sicher, warum dies nicht die am besten bewertete Antwort ist. Es ist sauber, folgt den Mustern von Newtonoft und ist einfach zu handhaben. Das einzige, was ich hinzufügen möchte, ist, dass Sie es global einrichten können mitJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M
1
Vergiss nicht, das macht eigentlich nicht das, wonach der Fragesteller fragt. Dies ist im Grunde eine Neuerstellung des JsonIgnore. Die Eigenschaft wird nicht für die Serialisierung übersprungen, sondern während der Deserialisierung festgelegt, da die GetSerializableMembers-Methode nicht erkennen kann, ob es sich um eine Lese- oder Schreibmethode handelt, sodass Sie die Eigenschaften für beide ausschließen. Diese Lösung funktioniert nicht.
Matt M
7

Verwenden Sie die Setter-Eigenschaft:

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { _ignoreOnSerializing = value; } }

[JsonIgnore]
private string _ignoreOnSerializing;

[JsonIgnore]
public string IgnoreOnSerializing
{
    get { return this._ignoreOnSerializing; }
    set { this._ignoreOnSerializing = value; }
}

Ich hoffe das hilft.

Tho Ho
quelle
1
Vielen Dank. Beachten Sie, dass die JsonProperty einen Großbuchstaben haben sollte IgnoreOnSerializing, der der Eigenschaft entspricht. Ich empfehle die Verwendung nameof(IgnoreOnSerializing), um magische Zeichenfolgen im Falle einer Umbenennung zu vermeiden.
Bendik August Nesbø
5

Nachdem ich ziemlich lange gesucht hatte, wie man eine Klasseneigenschaft als de-serialisierbar und NICHT serialisierbar kennzeichnet, stellte ich fest, dass es so etwas überhaupt nicht gibt. Also habe ich eine Lösung gefunden, die zwei verschiedene Bibliotheken oder Serialisierungstechniken kombiniert (System.Runtime.Serialization.Json & Newtonsoft.Json) und für mich wie folgt funktioniert:

  • Kennzeichnen Sie alle Ihre Klassen und Unterklassen als "DataContract".
  • Kennzeichnen Sie alle Eigenschaften Ihrer Klasse und Unterklassen als "DataMember".
  • Kennzeichnen Sie alle Eigenschaften Ihrer Klasse und Unterklassen als "JsonProperty", mit Ausnahme derjenigen, die nicht serialisiert werden sollen.
  • Markieren Sie nun die Eigenschaften, die NICHT als "JsonIgnore" serialisiert werden sollen.
  • Serialisieren Sie dann mit "Newtonsoft.Json.JsonConvert.SerializeObject" und de-Serialisieren Sie mit "System.Runtime.Serialization.Json.DataContractJsonSerializer".

    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Runtime.Serialization.Json;
    using System.Text;
    
    namespace LUM_Win.model
    {
        [DataContract]
        public class User
        {
            public User() { }
            public User(String JSONObject)
            {
                MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(JSONObject));
                DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(User));
    
                User user = (User)dataContractJsonSerializer.ReadObject(stream);
                this.ID = user.ID;
                this.Country = user.Country;
                this.FirstName = user.FirstName;
                this.LastName = user.LastName;
                this.Nickname = user.Nickname;
                this.PhoneNumber = user.PhoneNumber;
                this.DisplayPicture = user.DisplayPicture;
                this.IsRegistred = user.IsRegistred;
                this.IsConfirmed = user.IsConfirmed;
                this.VerificationCode = user.VerificationCode;
                this.Meetings = user.Meetings;
            }
    
            [DataMember(Name = "_id")]
            [JsonProperty(PropertyName = "_id")]
            public String ID { get; set; }
    
            [DataMember(Name = "country")]
            [JsonProperty(PropertyName = "country")]
            public String Country { get; set; }
    
            [DataMember(Name = "firstname")]
            [JsonProperty(PropertyName = "firstname")]
            public String FirstName { get; set; }
    
            [DataMember(Name = "lastname")]
            [JsonProperty(PropertyName = "lastname")]
            public String LastName { get; set; }
    
            [DataMember(Name = "nickname")]
            [JsonProperty(PropertyName = "nickname")]
            public String Nickname { get; set; }
    
            [DataMember(Name = "number")]
            [JsonProperty(PropertyName = "number")]
            public String PhoneNumber { get; set; }
    
            [DataMember(Name = "thumbnail")]
            [JsonProperty(PropertyName = "thumbnail")]
            public String DisplayPicture { get; set; }
    
            [DataMember(Name = "registered")]
            [JsonProperty(PropertyName = "registered")]
            public bool IsRegistred { get; set; }
    
            [DataMember(Name = "confirmed")]
            [JsonProperty(PropertyName = "confirmed")]
            public bool IsConfirmed { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "verification_code")]
            public String VerificationCode { get; set; }
    
            [JsonIgnore]
            [DataMember(Name = "meeting_ids")]
            public List<Meeting> Meetings { get; set; }
    
            public String toJSONString()
            {
                return JsonConvert.SerializeObject(this, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            }
        }
    }

Hoffentlich hilft das ...

Ahmed Abulazm
quelle
1
Bravo Ahmed Abulazm. danke, es hat mich vor viel Arbeit bewahrt. :)
Sike12
2

In Bezug auf die Lösung von @ ThoHo ist die Verwendung des Setters eigentlich alles, was benötigt wird, ohne zusätzliche Tags.

Für mich hatte ich zuvor eine einzige Referenz-ID, die ich laden und zur neuen Sammlung von Referenz-IDs hinzufügen wollte. Durch Ändern der Definition der Referenz-ID, um nur eine Setter-Methode zu enthalten, die den Wert zur neuen Sammlung hinzufügte. Json kann den Wert nicht zurückschreiben, wenn die Eigenschaft kein get hat. Methode.

// Old property that I want to read from Json, but never write again. No getter.
public Guid RefId { set { RefIds.Add(value); } }

// New property that will be in use from now on. Both setter and getter.
public ICollection<Guid> RefIds { get; set; }

Diese Klasse ist jetzt abwärtskompatibel mit der vorherigen Version und speichert die RefIds nur für die neuen Versionen.

simo.3792
quelle
1

Um auf der Antwort von Tho Ho aufzubauen, kann dies auch für Felder verwendet werden.

[JsonProperty(nameof(IgnoreOnSerializing))]
public string IgnoreOnSerializingSetter { set { IgnoreOnSerializing = value; } }

[JsonIgnore]
public string IgnoreOnSerializing;
Bendik August Nesbø
quelle
1

Abhängig davon, wo in der Anwendung dies stattfindet und ob es sich nur um eine Eigenschaft handelt, können Sie dies manuell tun, indem Sie den Eigenschaftswert auf null setzen. Anschließend können Sie im Modell angeben, dass die Eigenschaft ignoriert werden soll, wenn der Wert null ist:

[JsonProperty(NullValueHandling = NullValue.Ignore)]
public string MyProperty { get; set; }

Wenn Sie an einer ASP.NET Core-Webanwendung arbeiten, können Sie dies global für alle Eigenschaften in allen Modellen festlegen, indem Sie dies in Ihrer Datei Startup.cs festlegen:

public void ConfigureServices(IServiceCollection services) {
    // other configuration here
    services.AddMvc()
        .AddJsonOptions(options => options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore);
}
OzzyTheGiant
quelle
0

Gibt es eine einfache Möglichkeit, eine Eigenschaft eines C # -Objekts zu markieren (z. B. mit Attributen), sodass Json.net sie NUR beim Serialisieren ignoriert, beim Deserialisieren jedoch beachtet?

Der einfachste Weg, den ich zum Zeitpunkt dieses Schreibens gefunden habe, besteht darin, diese Logik in Ihren IContractResolver aufzunehmen .

Beispielcode vom obigen Link hier für die Nachwelt kopiert:

public class Employee
{
    public string Name { get; set; }
    public Employee Manager { get; set; }

    public bool ShouldSerializeManager()
    {
        // don't serialize the Manager property if an employee is their own manager
        return (Manager != this);
    }
}

public class ShouldSerializeContractResolver : DefaultContractResolver
{
    public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
        {
            property.ShouldSerialize =
                instance =>
                {
                    Employee e = (Employee)instance;
                    return e.Manager != e;
                };
        }

        return property;
    }
}

Alle Antworten sind gut, aber dieser Ansatz schien der sauberste Weg zu sein. Ich habe dies tatsächlich implementiert, indem ich nach einem Attribut in der Eigenschaft für SkipSerialize und SkipDeserialize gesucht habe, damit Sie einfach jede Klasse markieren können, die Sie steuern. Gute Frage!

Keine Rückerstattung Keine Rückgabe
quelle