Dokonanie deserializacji właściwości, ale nie serializacji z json.net


124

Mamy kilka plików konfiguracyjnych, które zostały wygenerowane przez serializację obiektów C # za pomocą Json.net.

Chcielibyśmy przeprowadzić migrację jednej właściwości klasy serializowanej z prostej właściwości wyliczeniowej do właściwości klasy.

Jednym prostym sposobem na zrobienie tego byłoby pozostawienie starej właściwości enum w klasie i zorganizowanie odczytywania tej właściwości przez Json.net podczas ładowania konfiguracji, ale nie zapisywanie jej ponownie przy następnym serializowaniu obiektu. Oddzielnie zajmiemy się generowaniem nowej klasy ze starego wyliczenia.

Czy istnieje prosty sposób na oznaczenie (np. Atrybutami) właściwości obiektu C #, aby Json.net zignorował ją TYLKO podczas serializacji, ale zajmie się nią podczas deserializacji?


A co z niestandardowym konwerterem: możesz użyć go jako atrybutu w swojej właściwości, zastąpić ReadJson i WriteJson różnymi komportowaniami, nie? Przykład (nie dokładnie to, czego potrzebujesz, ale ...) weblogs.asp.net/thangchung/archive/2010/08/26/ ...
Raphaël Althaus

Atrybut OnDeserialized może być dla Ciebie
obejściem

Czy nie powinno to być możliwe przy użyciu atrybutu „[JsonIgnore]”? james.newtonking.com/archive/2009/10/23/…
Juri

Czy jesteś w stanie rozwinąć, w jaki sposób można to wykorzystać tylko w jednym kierunku, zgodnie z ostatnim akapitem q?
Will Dean

Możliwe jest użycie [JsonIgnore] w połączeniu z drugorzędnym prywatnym ustawiaczem, który jest ozdobiony atrybutem [JsonProperty]. Jest też kilka innych prostych rozwiązań. Dodałem szczegółowy opis.
Brian Rogers

Odpowiedzi:


133

W rzeczywistości istnieje kilka dość prostych podejść, których możesz użyć, aby osiągnąć pożądany rezultat.

Załóżmy na przykład, że masz obecnie zdefiniowane klasy w następujący sposób:

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

enum Fizz { Alpha, Beta, Gamma }

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

I chcesz to zrobić:

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

Aby to uzyskać:

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

Podejście 1: Dodaj metodę ShouldSerialize

Json.NET może warunkowo serializować właściwości, szukając odpowiednich ShouldSerializemetod w klasie.

Aby użyć tej funkcji, dodaj ShouldSerializeBlah()do klasy metodę logiczną , w której Blahzostanie zastąpiona nazwą właściwości, której nie chcesz serializować. Spraw, aby implementacja tej metody zawsze zwracała false.

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

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Uwaga: jeśli podoba ci się to podejście, ale nie chcesz zagmatwać publicznego interfejsu swojej klasy przez wprowadzenie ShouldSerializemetody, możesz użyć narzędzia an, IContractResolveraby zrobić to samo programowo. Zobacz Serializacja właściwości warunkowych w dokumentacji.

Podejście 2: manipuluj JSON za pomocą JObjects

Zamiast używać JsonConvert.SerializeObjectdo serializacji, załaduj obiekt config do a JObject, a następnie po prostu usuń niechcianą właściwość z JSON przed jej zapisaniem. To tylko kilka dodatkowych linii kodu.

JObject jo = JObject.FromObject(config);

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

json = jo.ToString();

Podejście 3: Sprytne (nad) wykorzystanie atrybutów

  1. Zastosuj [JsonIgnore]atrybut do właściwości, której nie chcesz serializować.
  2. Dodaj alternatywną, prywatną metodę ustawiającą właściwości do klasy o tym samym typie, co oryginalna właściwość. Spraw, aby implementacja tej właściwości ustawiała oryginalną właściwość.
  3. Zastosuj [JsonProperty]atrybut do alternatywnej metody ustawiającej, nadając jej taką samą nazwę JSON, jak oryginalna właściwość.

Oto poprawiona Configklasa:

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
Rozwiązaliśmy to w naszym projekcie (który używa wewnętrznego super zestawu modelu podstawowego, specyficznego dla integracji, gdzie żadna z właściwości nadklasy nie powinna być serializowana), ustawiając właściwości get na internal. Mając publiczne metody ustawiające, umożliwiono Web Api ustawianie właściwości, ale uniemożliwiło to ich serializację.
Daniel Saidi

7
W połączeniu z używaniem JsonPropertyAttribute, od C # 6.0 można użyć nameofsłowa kluczowego zamiast używać „magicznych ciągów”. To sprawia, że ​​refaktoryzacja jest o wiele łatwiejsza i niezawodna - a ponadto, jeśli przegapisz zmianę nazwy jakichkolwiek wystąpień, kompilator i tak Cię ostrzeże. Używając przykładu @ Briana, użycie byłoby następujące:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James

1
Używanie nameof () w deklaracjach JsonProperty jest złym pomysłem, szczególnie w tym starszym scenariuszu. JSON reprezentuje zewnętrzną (i miejmy nadzieję wieczną) umowę z innym interfejsem i zdecydowanie NIE chcesz zmieniać nazwy właściwości JSON, jeśli dokonujesz refaktoryzacji. Naruszałbyś kompatybilność wszystkich istniejących plików JSON i komponentów, które generują JSON w tym formacie. W rzeczywistości lepiej byłoby umieścić JsonProperty (…) z pełną nazwą w każdej zserializowanej właściwości, aby upewnić się, że nie ulegną zmianie, jeśli później zmienisz nazwę właściwości.
Ammo Goettsch

36

W każdej sytuacji, w której właściwość tylko do deserializacji może być oznaczona jako wewnętrzna, istnieje niezwykle proste rozwiązanie, które w ogóle nie zależy od atrybutów. Po prostu oznacz właściwość jako wewnętrzną get, ale publiczną:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Powoduje to poprawną deserializację przy użyciu ustawień domyślnych / programów rozpoznawania nazw / itp., Ale właściwość jest usuwana z serializowanych danych wyjściowych.


Proste, ale sprytne rozwiązanie.
hbulens

Należy pamiętać, że właściwość zostanie również zignorowana przez moduł walidacji. (Więc nie możesz już oznaczać tego jako [Wymagane] do deserializacji, ponieważ opiera się to na getmetodzie publicznej ).
Martin Hansen

2
To nie działa z ani z internalani private. Jest zawsze serializowany.
Paul

To nie zadziałało dla mnie. Podczas deserializacji wystąpił błąd „Nie znaleziono właściwości”.
Drew Sumido

31

Lubię trzymać się atrybutów w tym przypadku, oto metoda, której używam, gdy muszę deserializować właściwość, ale nie serializować jej lub odwrotnie.

KROK 1 - Utwórz atrybut niestandardowy

public class JsonIgnoreSerializationAttribute : Attribute { }

KROK 2 - Utwórz niestandardową umowę 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>();
    }
}

KROK 3 - Dodaj atrybut tam, gdzie serializacja nie jest potrzebna, ale deserializacja jest

    [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

KROK 4 - Użyj go

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

Mam nadzieję że to pomoże! Warto również zauważyć, że spowoduje to również zignorowanie właściwości, gdy nastąpi deserializacja, kiedy derserializuję, po prostu używam konwertera w konwencjonalny sposób.

JsonConvert.DeserializeObject<MyType>(myString);

Dzięki za tę pomocną realizację. Czy istnieje sposób, aby rozszerzyć podstawową implementację GetSerializableMemberszamiast całkowicie ją zastąpić?
Alain

4
Nieważne, właśnie zdałem sobie sprawę, że to tak proste, jak:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Alain

nie wiem, dlaczego nie jest to najwyżej oceniana odpowiedź. jest czysty, podąża za wzorami Newtonsoft i jest łatwy do wykonania. Jedyne, co chciałbym dodać, to to, że możesz ustawić to globalnie za pomocąJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M

1
nieważne, to w rzeczywistości nie robi tego, o co prosi pytający. jest to po prostu odtworzenie JsonIgnore. nie pomija właściwości do serializacji, ale ustawia właściwość podczas deserializacji, ponieważ metoda GetSerializableMembers nie może wiedzieć, czy jest to odczyt, czy zapis, więc wykluczasz właściwości dla obu. to rozwiązanie nie działa.
Matt M

7

Użyj właściwości ustawiającej:

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

Mam nadzieję, że to pomoże.


1
Dzięki. Należy zauważyć, że właściwość JsonProperty powinna mieć wielką literę IgnoreOnSerializingrówną właściwości. Zalecam używanie, nameof(IgnoreOnSerializing)aby uniknąć magicznego ciągu w przypadku zmiany nazwy.
Bendik August Nesbø

5

Po tym, jak spędziłem dość długi czas na poszukiwaniu, jak oznaczyć właściwość klasy jako de-serializowalną, a nie serializowalną, stwierdziłem, że w ogóle nie ma czegoś takiego; więc wymyśliłem rozwiązanie, które łączy dwie różne biblioteki lub techniki serializacji (System.Runtime.Serialization.Json i Newtonsoft.Json) i działało dla mnie w następujący sposób:

  • oflaguj wszystkie swoje klasy i podklasy jako „DataContract”.
  • oznacz wszystkie właściwości swojej klasy i podklas jako „DataMember”.
  • oflaguj wszystkie właściwości swojej klasy i podklas jako „JsonProperty”, z wyjątkiem tych, których nie chcesz serializować.
  • teraz oflaguj właściwości, których NIE chcesz, aby były serializowane jako „JsonIgnore”.
  • następnie Serializuj za pomocą „Newtonsoft.Json.JsonConvert.SerializeObject” i De-Serialize za pomocą „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 });
            }
        }
    }

Mam nadzieję, że to pomoże ...


1
Bravo Ahmed Abulazm. dzięki, że zaoszczędziłem dużo pracy. :)
Sike12

2

w odniesieniu do rozwiązania @ ThoHo, użycie settera to właściwie wszystko, czego potrzeba, bez dodatkowych tagów.

Wcześniej miałem jeden identyfikator referencyjny, który chciałem załadować i dodać do nowej kolekcji identyfikatorów referencyjnych. Zmieniając definicję identyfikatora odwołania, aby zawierała tylko metodę ustawiającą, która dodała wartość do nowej kolekcji. Json nie może zapisać wartości z powrotem, jeśli Property nie ma get; metoda.

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

Ta klasa jest teraz wstecznie kompatybilna z poprzednią wersją i zapisuje RefIds tylko dla nowych wersji.


1

Aby rozwinąć odpowiedź Tho Ho, można to również wykorzystać do pól.

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

[JsonIgnore]
public string IgnoreOnSerializing;

1

W zależności od tego, gdzie w aplikacji ma to miejsce i jeśli jest to tylko jedna właściwość, jednym ręcznym sposobem można to zrobić, ustawiając wartość właściwości na null, a następnie w modelu można określić, że właściwość ma być ignorowana, jeśli wartość jest równa null:

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

Jeśli pracujesz nad aplikacją internetową ASP.NET Core, możesz globalnie ustawić to dla wszystkich właściwości we wszystkich modelach, ustawiając to w pliku Startup.cs:

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


0

Czy istnieje prosty sposób na oznaczenie (np. Atrybutami) właściwości obiektu C #, aby Json.net zignorował ją TYLKO podczas serializacji, ale zajmie się nią podczas deserializacji?

Najłatwiejszym sposobem, jaki znalazłem podczas pisania tego tekstu, jest włączenie tej logiki do twojego IContractResolver .

Przykładowy kod z powyższego linku skopiowany tutaj dla potomności:

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

Wszystkie odpowiedzi są dobre, ale takie podejście wydawało się najczystsze. W rzeczywistości zaimplementowałem to, szukając atrybutu we właściwości dla SkipSerialize i SkipDeserialize, dzięki czemu możesz po prostu oznaczyć dowolną kontrolowaną klasę. Świetne pytanie!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.