Deserializzare una proprietà ma non serializzarla con json.net


124

Abbiamo alcuni file di configurazione che sono stati generati serializzando oggetti C # con Json.net.

Vorremmo migrare una proprietà della classe serializzata dall'essere una semplice proprietà enum a una proprietà di classe.

Un modo semplice per farlo sarebbe lasciare la vecchia proprietà enum sulla classe e fare in modo che Json.net legga questa proprietà quando carichiamo la configurazione, ma non salvarla di nuovo quando serializzeremo l'oggetto. Ci occuperemo di generare separatamente la nuova classe dalla vecchia enumerazione.

Esiste un modo semplice per contrassegnare (ad esempio con attributi) una proprietà di un oggetto C #, in modo che Json.net lo ignori SOLO durante la serializzazione, ma si occupi di esso durante la deserializzazione?


Che ne dici di un convertitore personalizzato: puoi usarlo come attributo sulla tua proprietà, sovrascrivere ReadJson e WriteJson con comportamenti diversi, no? Esempio (non esattamente quello che ti serve, ma ...) weblogs.asp.net/thangchung/archive/2010/08/26/…
Raphaël Althaus

L'attributo OnDeserialized può essere una soluzione per te
Estefany Velez,

Non dovrebbe essere possibile utilizzando l'attributo "[JsonIgnore]" ?? james.newtonking.com/archive/2009/10/23/…
Juri

Sei in grado di espandere su come questo può essere utilizzato in una sola direzione, come nell'ultimo paragrafo della q?
Will Dean

È possibile utilizzare [JsonIgnore] in combinazione con un setter privato secondario decorato con un attributo [JsonProperty]. Ci sono anche un paio di altre semplici soluzioni. Ho aggiunto un resoconto dettagliato.
Brian Rogers

Risposte:


133

Esistono in realtà diversi approcci abbastanza semplici che puoi utilizzare per ottenere il risultato desiderato.

Supponiamo, ad esempio, che le tue classi siano attualmente definite in questo modo:

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

enum Fizz { Alpha, Beta, Gamma }

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

E vuoi fare questo:

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

Per ottenere questo:

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

Approccio 1: aggiungi un metodo ShouldSerialize

Json.NET ha la capacità di serializzare in modo condizionale le proprietà cercando i corrispondenti ShouldSerialize metodi nella classe.

Per utilizzare questa funzionalità, aggiungi un ShouldSerializeBlah()metodo booleano alla tua classe dove Blahviene sostituito con il nome della proprietà che non desideri serializzare. Fare in modo che l'implementazione di questo metodo ritorni sempre false.

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

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Nota: se ti piace questo approccio ma non vuoi confondere l'interfaccia pubblica della tua classe introducendo un ShouldSerializemetodo, puoi usare an IContractResolverper fare la stessa cosa a livello di programmazione. Vedere Serializzazione condizionale delle proprietà nella documentazione.

Approccio 2: manipola il JSON con JObjects

Invece di utilizzare JsonConvert.SerializeObjectper eseguire la serializzazione, caricare l'oggetto di configurazione in un JObject, quindi rimuovere semplicemente la proprietà indesiderata dal JSON prima di scriverlo. Sono solo un paio di righe di codice extra.

JObject jo = JObject.FromObject(config);

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

json = jo.ToString();

Approccio 3: uso intelligente (ab) degli attributi

  1. Applicare a [JsonIgnore] attributo alla proprietà che non si desidera serializzare.
  2. Aggiungi un alternativo, privato setter di proprietà alla classe con lo stesso tipo della proprietà originale. Rendere l'implementazione di quella proprietà impostare la proprietà originale.
  3. Applica un [JsonProperty]attributo al setter alternativo, assegnandogli lo stesso nome JSON della proprietà originale.

Ecco la Configclasse rivista :

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

7
L'abbiamo risolto nel nostro progetto (che utilizza un super set interno specifico per l'integrazione del modello base, in cui nessuna delle proprietà della superclasse dovrebbe essere serializzata), impostando le proprietà get su internal. La presenza di setter pubblici consentiva a Web Api di impostare le proprietà, ma ne impediva la serializzazione.
Daniel Saidi

7
Insieme all'uso di JsonPropertyAttribute, da C # 6.0 è possibile utilizzare la nameofparola chiave invece di utilizzare "stringhe magiche". Questo rende il refactoring molto più semplice ea prova di errore, inoltre, se non rinominando alcuna occorrenza, il compilatore ti avviserà comunque. Utilizzando l'esempio di @ Brian, l'utilizzo sarebbe questo:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James

1
È una cattiva idea usare nameof () nelle dichiarazioni di JsonProperty, in particolare in questo scenario legacy. Il JSON rappresenta un contratto esterno (e si spera eterno) con un'altra interfaccia e NON si desidera assolutamente modificare il nome della proprietà JSON se si esegue il refactoring. Si interromperà la compatibilità con tutti i file e i componenti JSON esistenti che generano JSON in questo formato. In effetti, faresti meglio a mettere JsonProperty (...) con il nome completo su ogni proprietà serializzata per assicurarti che non cambino se in seguito rinomini la proprietà.
Ammo Goettsch

36

Per qualsiasi situazione in cui è accettabile che la proprietà di sola deserializzazione sia contrassegnata come interna, esiste una soluzione straordinariamente semplice che non dipende affatto dagli attributi. Contrassegna semplicemente la proprietà come get interno, ma set pubblico:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Ciò si traduce in una deserializzazione corretta utilizzando le impostazioni predefinite / resolver / ecc., Ma la proprietà viene rimossa dall'output serializzato.


Soluzione semplice ma intelligente.
hbulens

Si noti che la proprietà verrà ignorata anche dal modulo di convalida. (Quindi non è più possibile contrassegnarlo come [Obbligatorio] per la deserializzazione, poiché si basa su un getmetodo pubblico ).
Martin Hansen

2
Questo non funziona né con internalprivate. È sempre serializzato.
Paul

Questo non ha funzionato per me. Si è verificato un errore di proprietà non trovata durante la deserializzazione.
Drew Sumido

31

Mi piace attenermi agli attributi su questo, ecco il metodo che uso quando ho bisogno di deserializzare una proprietà ma non di serializzarla o viceversa.

PASSAGGIO 1 - Crea l'attributo personalizzato

public class JsonIgnoreSerializationAttribute : Attribute { }

FASE 2 - Crea un Reslover contratto personalizzato

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

PASSAGGIO 3 - Aggiungere un attributo in cui la serializzazione non è necessaria ma la deserializzazione lo è

    [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

FASE 4 - Usalo

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

Spero che questo ti aiuti! Inoltre vale la pena notare che questo ignorerà anche le proprietà quando avviene la deserializzazione, quando sto derserializzando semplicemente uso il convertitore in modo convenzionale.

JsonConvert.DeserializeObject<MyType>(myString);

Grazie per questa utile implementazione. C'è un modo per estendere l'implementazione di base GetSerializableMemberspiuttosto che sovrascriverla del tutto?
Alain

4
Non importa, ho appena capito che era semplice come:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Alain

non sono sicuro del motivo per cui questa non è la risposta più votata. è pulito, segue gli schemi di newtonsoft ed è facile da fare. l'unica cosa che aggiungerei è che puoi configurarlo a livello globale utilizzandoJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M

1
non importa, questo in realtà non fa ciò che il richiedente chiede. questo è fondamentalmente ricreare il JsonIgnore. non salta la proprietà per la serializzazione ma imposta la proprietà durante la deserializzazione perché non c'è modo per il metodo GetSerializableMembers di sapere se è una lettura o una scrittura, quindi stai escludendo le proprietà per entrambi. questa soluzione non funziona.
Matt M

7

Usa proprietà setter:

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

Spero che questo aiuto.


1
Grazie. Notare che JsonProperty dovrebbe avere una maiuscola IgnoreOnSerializing, uguale alla proprietà. Consiglio di utilizzare nameof(IgnoreOnSerializing)per evitare la stringa magica, in caso di rinomina.
Bendik August Nesbø

5

Dopo aver passato un bel po 'di tempo a cercare come contrassegnare una proprietà di classe per essere de-serializzabile e NON serializzabile, ho scoperto che non esiste nulla di simile per farlo; quindi ho escogitato una soluzione che combina due diverse librerie o tecniche di serializzazione (System.Runtime.Serialization.Json & Newtonsoft.Json) e ha funzionato per me in questo modo:

  • contrassegna tutte le tue classi e sottoclassi come "DataContract".
  • contrassegna tutte le proprietà della tua classe e sottoclasse come "DataMember".
  • contrassegna tutte le proprietà della tua classe e sottoclasse come "JsonProperty" eccetto quelle per cui non vuoi che vengano serializzate.
  • ora contrassegna le proprietà che NON vuoi che venga serializzato come "JsonIgnore".
  • quindi Serializza utilizzando "Newtonsoft.Json.JsonConvert.SerializeObject" e De-Serialize utilizzando "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 });
            }
        }
    }

Spero che aiuti ...


1
Bravo Ahmed Abulazm. grazie mi ha salvato da molto lavoro. :)
Sike12

2

con riferimento alla soluzione di @ ThoHo, l'utilizzo del setter è in realtà tutto ciò che è necessario, senza tag aggiuntivi.

Per me in precedenza avevo un unico ID di riferimento, che volevo caricare e aggiungere alla nuova raccolta di ID di riferimento. Modificando la definizione dell'ID di riferimento per contenere solo un metodo setter, che ha aggiunto il valore alla nuova raccolta. Json non può riscrivere il valore se la proprietà non ha un get; metodo.

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

Questa classe è ora retrocompatibile con la versione precedente e salva solo il file RefId per le nuove versioni.


1

Per costruire sulla risposta di Tho Ho, questo può essere utilizzato anche per i campi.

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

[JsonIgnore]
public string IgnoreOnSerializing;

1

A seconda di dove nell'applicazione questo avviene e se è solo una proprietà, un manuale modo per farlo è impostando il valore della proprietà su null e quindi sul modello è possibile specificare che la proprietà venga ignorata se il valore è null:

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

Se stai lavorando su un'app Web ASP.NET Core, puoi impostarla globalmente per tutte le proprietà in tutti i modelli impostandola nel file Startup.cs:

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

0

Se usi JsonConvert, IgnoreDataMemberAttribute va bene. La mia libreria standard non aggiorna Newton.Json e io uso [IgnoreDataMember] per controllare la serializzazione degli oggetti.

Dal documento della guida di Newton.net .


0

Esiste un modo semplice per contrassegnare (ad esempio con attributi) una proprietà di un oggetto C #, in modo che Json.net lo ignori SOLO durante la serializzazione, ma si occupi di esso durante la deserializzazione?

Il modo più semplice che ho trovato al momento della stesura di questo articolo è includere questa logica nel tuo IContractResolver .

Codice di esempio dal link sopra copiato qui per i posteri:

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

Tutte le risposte sono buone ma questo approccio sembrava il modo più pulito. In realtà l'ho implementato cercando un attributo nella proprietà per SkipSerialize e SkipDeserialize in modo da poter contrassegnare qualsiasi classe che controlli. Ottima domanda!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.