Faire une propriété désérialiser mais pas sérialiser avec json.net


124

Nous avons quelques fichiers de configuration qui ont été générés en sérialisant des objets C # avec Json.net.

Nous aimerions migrer une propriété de la classe sérialisée loin d'être une simple propriété enum vers une propriété de classe.

Un moyen simple de le faire serait de laisser l'ancienne propriété enum sur la classe et de faire en sorte que Json.net lise cette propriété lorsque nous chargeons la configuration, mais pas de la sauvegarder lors de la prochaine sérialisation de l'objet. Nous traiterons la génération de la nouvelle classe à partir de l'ancienne énumération séparément.

Existe-t-il un moyen simple de marquer (par exemple avec des attributs) une propriété d'un objet C #, de sorte que Json.net l'ignore UNIQUEMENT lors de la sérialisation, mais s'en occupe lors de la désérialisation?


Qu'en est-il d'un convertisseur personnalisé: vous pouvez l'utiliser comme attribut sur votre propriété, remplacer ReadJson et WriteJson avec des comportements différents, non? Exemple (pas exactement ce dont vous avez besoin, mais ...) weblogs.asp.net/thangchung/archive/2010/08/26
Raphaël Althaus

L'attribut OnDeserialized peut être une solution de contournement pour vous
Estefany Velez

Cela ne devrait-il pas être possible en utilisant l'attribut `[JsonIgnore] '?? james.newtonking.com/archive/2009/10/23/…
Juri

Êtes-vous en mesure d'expliquer comment cela peut être utilisé dans une seule direction, conformément au dernier paragraphe du q?
Will Dean

Il est possible d'utiliser [JsonIgnore] en combinaison avec un setter privé secondaire décoré d'un attribut [JsonProperty]. Il existe également quelques autres solutions simples. J'ai ajouté un résumé détaillé.
Brian Rogers

Réponses:


133

Il existe en fait plusieurs approches assez simples que vous pouvez utiliser pour obtenir le résultat souhaité.

Supposons, par exemple, que vos classes soient actuellement définies comme ceci:

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

enum Fizz { Alpha, Beta, Gamma }

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

Et vous voulez faire ceci:

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

Pour obtenir ceci:

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

Approche 1: ajouter une méthode ShouldSerialize

Json.NET a la capacité de sérialiser conditionnellement les propriétés en recherchant les ShouldSerializeméthodes correspondantes dans la classe.

Pour utiliser cette fonctionnalité, ajoutez une ShouldSerializeBlah()méthode booléenne à votre classe où Blahest remplacé par le nom de la propriété que vous ne souhaitez pas sérialiser. Rendre l'implémentation de cette méthode toujours renvoyée false.

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

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Remarque: si vous aimez cette approche mais que vous ne voulez pas brouiller l'interface publique de votre classe en introduisant une ShouldSerializeméthode, vous pouvez utiliser un IContractResolverpour faire la même chose par programme. Voir Sérialisation conditionnelle des propriétés dans la documentation.

Approche 2: Manipuler le JSON avec JObjects

Au lieu d'utiliser JsonConvert.SerializeObjectpour faire la sérialisation, chargez l'objet de configuration dans un JObject, puis supprimez simplement la propriété indésirable du JSON avant de l'écrire. C'est juste quelques lignes de code supplémentaires.

JObject jo = JObject.FromObject(config);

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

json = jo.ToString();

Approche 3: Utilisation intelligente (ab) des attributs

  1. Appliquez un [JsonIgnore]attribut à la propriété que vous ne souhaitez pas sérialiser.
  2. Ajoutez un autre setter de propriété privée à la classe avec le même type que la propriété d'origine. Faites de l'implémentation de cette propriété la propriété d'origine.
  3. Appliquez un [JsonProperty]attribut à l'autre setter, en lui donnant le même nom JSON que la propriété d'origine.

Voici la Configclasse révisée :

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
Nous l'avons résolu dans notre projet (qui utilise un super-ensemble interne spécifique à l'intégration du modèle de base, où aucune des propriétés de la superclasse ne doit être sérialisée), en définissant les propriétés get-properties sur internal. Le fait d'avoir des setters publics a permis à Web Api de définir les propriétés, mais l'a empêché de les sérialiser.
Daniel Saidi

7
En conjonction avec l'utilisation de JsonPropertyAttribute, à partir de C # 6.0, vous pouvez utiliser le nameofmot - clé au lieu d'utiliser des "chaînes magiques". Cela rend la refactorisation beaucoup plus facile et infaillible - de plus, si vous oubliez de renommer des occurrences, le compilateur vous avertira de toute façon. En utilisant l'exemple de @ Brian, l'utilisation serait la suivante:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James

1
C'est une mauvaise idée d'utiliser nameof () dans les déclarations JsonProperty, en particulier dans ce scénario hérité. Le JSON représente un contrat externe (et espérons-le éternel) avec une autre interface, et vous ne voulez certainement PAS changer le nom de la propriété JSON si vous refactorisez. Vous rompriez la compatibilité avec tous les fichiers et composants JSON existants qui génèrent du JSON dans ce format. En fait, vous feriez mieux de mettre JsonProperty (…) avec le nom complet sur chaque propriété sérialisée pour vous assurer qu'elles ne changent pas si vous renommez ultérieurement la propriété.
Ammo Goettsch le

36

Pour toute situation où il est acceptable que votre propriété de désérialisation uniquement soit marquée comme interne, il existe une solution remarquablement simple qui ne dépend pas du tout d'attributs. Marquez simplement la propriété comme interne get, mais public set:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Cela entraîne une désérialisation correcte à l'aide des paramètres par défaut / résolveurs / etc., Mais la propriété est supprimée de la sortie sérialisée.


Solution simple mais intelligente.
hbulens

Veuillez noter que la propriété sera également ignorée par le module de validation. (Vous ne pouvez donc plus le marquer comme [Obligatoire] pour la désérialisation, car cela repose sur une getméthode publique ).
Martin Hansen

2
Cela ne fonctionne ni avec internalni private. Il est toujours sérialisé.
Paul

Cela n'a pas fonctionné pour moi. Vous avez une erreur de propriété introuvable lors de la désérialisation.
Drew Sumido

31

J'aime m'en tenir aux attributs sur celui-ci, voici la méthode que j'utilise lorsque j'ai besoin de désérialiser une propriété mais pas de la sérialiser ou vice versa.

ÉTAPE 1 - Créez l'attribut personnalisé

public class JsonIgnoreSerializationAttribute : Attribute { }

ÉTAPE 2 - Créer un Reslover de contrat personnalisé

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

ÉTAPE 3 - Ajouter un attribut où la sérialisation n'est pas nécessaire mais la désérialisation est

    [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

ÉTAPE 4 - Utilisez-le

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

J'espère que cela t'aides! Il convient également de noter que cela ignorera également les propriétés lorsque la désérialisation se produit, lorsque je désérialise, j'utilise simplement le convertisseur de manière conventionnelle.

JsonConvert.DeserializeObject<MyType>(myString);

Merci pour cette mise en œuvre utile. Existe-t-il un moyen d'étendre l'implémentation de base GetSerializableMembersplutôt que de la remplacer entièrement?
Alain

4
Peu importe, je viens de réaliser que c'était aussi simple que:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Alain

Je ne sais pas pourquoi ce n'est pas la réponse la mieux notée. il est propre, suit les modèles de newtonsoft et est facile à faire. la seule chose que j'ajouterais, c'est que vous pouvez le configurer globalement en utilisantJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M

1
peu importe, cela ne fait pas vraiment ce que le demandeur demande. il s'agit essentiellement de recréer le JsonIgnore. il n'ignore pas la propriété pour la sérialisation mais définit la propriété lors de la désérialisation car il n'y a aucun moyen pour la méthode GetSerializableMembers de savoir s'il s'agit d'une lecture ou d'une écriture, vous excluez donc les propriétés pour les deux. cette solution ne fonctionne pas.
Matt M

7

Utilisez la propriété 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; }
}

J'espère que cette aide.


1
Merci. Notez que JsonProperty doit avoir une majuscule IgnoreOnSerializing, égale à la propriété. Je recommande d'utiliser nameof(IgnoreOnSerializing)pour éviter la chaîne magique, en cas de changement de nom.
Bendik August Nesbø

5

Après avoir passé un temps assez long à chercher comment marquer une propriété de classe comme étant dé-sérialisable et NON sérialisable, j'ai trouvé qu'il n'y avait rien de tel pour faire cela; j'ai donc proposé une solution qui combine deux bibliothèques ou techniques de sérialisation différentes (System.Runtime.Serialization.Json & Newtonsoft.Json) et cela a fonctionné pour moi comme suit:

  • marquez toutes vos classes et sous-classes comme "DataContract".
  • marquez toutes les propriétés de votre classe et sous-classes comme "DataMember".
  • marquez toutes les propriétés de votre classe et de vos sous-classes comme "JsonProperty" à l'exception de celles que vous souhaitez qu'elles ne soient pas sérialisées.
  • marquez maintenant les propriétés dont vous ne voulez PAS qu'elles soient sérialisées comme "JsonIgnore".
  • puis Sérialisez en utilisant "Newtonsoft.Json.JsonConvert.SerializeObject" et De-Serialize en utilisant "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 });
            }
        }
    }

J'espère que cela pourra aider ...


1
Bravo Ahmed Abulazm. merci, cela m'a épargné beaucoup de travail. :)
Sike12

2

en référence à la solution de @ ThoHo, l'utilisation du setter est en fait tout ce qui est nécessaire, sans balises supplémentaires.

Pour moi, j'avais auparavant un seul identifiant de référence, que je voulais charger et ajouter à la nouvelle collection d'identifiants de référence. En modifiant la définition de l'ID de référence pour qu'elle ne contienne qu'une méthode setter, qui a ajouté la valeur à la nouvelle collection. Json ne peut pas réécrire la valeur si la propriété n'a pas de get; méthode.

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

Cette classe est désormais rétrocompatible avec la version précédente et enregistre uniquement les RefIds pour les nouvelles versions.


1

Pour construire sur la réponse de Tho Ho, cela peut également être utilisé pour les champs.

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

[JsonIgnore]
public string IgnoreOnSerializing;

1

Selon l'endroit où cela se produit dans l'application et s'il ne s'agit que d'une seule propriété, vous pouvez le faire manuellement en définissant la valeur de la propriété sur null, puis sur le modèle, vous pouvez spécifier que la propriété soit ignorée si la valeur est nulle:

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

Si vous travaillez sur une application Web ASP.NET Core, vous pouvez définir globalement ceci pour toutes les propriétés de tous les modèles en définissant ceci dans votre fichier Startup.cs:

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

0

Si vous utilisez JsonConvert, IgnoreDataMemberAttribute est ok. Ma bibliothèque standard ne réfrence pas Newton.Json, et j'utilise [IgnoreDataMember] pour contrôler la sérialisation des objets.

À partir du document d'aide de Newton.net .


0

Existe-t-il un moyen simple de marquer (par exemple avec des attributs) une propriété d'un objet C #, de sorte que Json.net l'ignore UNIQUEMENT lors de la sérialisation, mais s'en occupe lors de la désérialisation?

Le moyen le plus simple que j'ai trouvé à ce jour est d'inclure cette logique dans votre IContractResolver .

Exemple de code du lien ci-dessus copié ici pour la postérité:

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

Toutes les réponses sont bonnes, mais cette approche semble être la manière la plus claire. J'ai en fait mis en œuvre cela en recherchant un attribut sur la propriété pour SkipSerialize et SkipDeserialize afin que vous puissiez simplement marquer n'importe quelle classe que vous contrôlez. Excellente question!

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.