Створення властивості десяріалізувати, але не серіалізувати за допомогою json.net


124

У нас є кілька конфігураційних файлів, які були створені серіалізацією об'єктів C # з Json.net

Ми хотіли б перенести одну властивість серіалізованого класу подалі від простого переліку властивостей enum у властивість класу.

Один з простих способів зробити це - залишити старий властивість enum у класі та домовитись, щоб Json.net прочитав цю властивість, коли ми завантажуємо конфігурацію, але не зберігати її знову, коли ми наступну серіалізуємо об’єкт. Ми будемо мати справу з генеруванням нового класу зі старого перерахунку окремо.

Чи є якийсь простий спосіб позначити (наприклад, з атрибутами) властивість об'єкта C #, щоб Json.net ігнорував його ТІЛЬКИ при серіалізації, але відвідував його під час десеріалізації?


А як щодо користувацького конвертера: ви можете використовувати його як атрибут у своїй власності, замінити ReadJson і WriteJson різними повідомленнями, ні? Приклад (не саме те, що потрібно, але ...) weblogs.asp.net/thangchung/archive/2010/08/26/…
Рафаель

Атрибут OnDeserialized може стати для вас
обробкою

Чи не може це бути можливо за допомогою атрибута "[JsonIgnore]" ?? james.newtonking.com/archive/2009/10/23/…
Juri

Чи можете ви розширити, як це можна використовувати лише в одному напрямку, відповідно до останнього абзацу q?
Буде Дін

Можна використовувати [JsonIgnore] у поєднанні з вторинним приватним сеттером, який прикрашений атрибутом [JsonProperty]. Є ще кілька простих рішень. Я додав докладний опис.
Брайан Роджерс

Відповіді:


133

Насправді є кілька досить простих підходів, якими ви можете скористатися для досягнення бажаного результату.

Припустимо, наприклад, що у вас зараз такі класи визначені так:

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

enum Fizz { Alpha, Beta, Gamma }

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

І ви хочете зробити це:

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

Щоб отримати це:

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

Підхід 1: Додайте метод ShouldSerialize

Json.NET має можливість умовно серіалізувати властивості, шукаючи відповідні ShouldSerializeметоди у класі.

Щоб скористатися цією функцією, додайте бульовий ShouldSerializeBlah()метод до свого класу, де Blahзамінено ім'я властивості, яке ви не хочете серіалізувати. Зробіть реалізацію цього методу завжди поверненою false.

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

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Примітка: якщо вам подобається такий підхід, але ви не хочете замучувати публічний інтерфейс свого класу, вводячи ShouldSerializeметод, ви можете використовувати програму, IContractResolverщоб зробити те ж саме в програмному порядку. Дивіться умовну серіалізацію майна в документації.

Підхід 2: Керуйте JSON за допомогою JObjects

Замість того, JsonConvert.SerializeObjectщоб використовувати серіалізацію, завантажте конфігураційний об’єкт у a JObject, а потім просто видаліть небажане властивість з JSON перед тим, як виписати його. Це лише пара зайвих рядків коду.

JObject jo = JObject.FromObject(config);

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

json = jo.ToString();

Підхід 3: Розумне (ab) використання атрибутів

  1. Застосуйте [JsonIgnore]атрибут до властивості, яке ви не хочете серіалізувати.
  2. Додайте альтернативний інсталятор приватної власності до класу з тим же типом, що і оригінальний ресурс. Зробити реалізацію цього властивості встановити початкову властивість.
  3. Застосуйте [JsonProperty]атрибут до альтернативного сеттера, давши йому те саме ім'я JSON, що і вихідне властивість.

Ось переглянений Configклас:

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
Ми вирішили це в нашому проекті (який використовує внутрішній інтегральний супер набір базової моделі, де жодна з властивостей надкласу не повинна бути серіалізована), встановивши властивості get-адреси на внутрішні. Загальнодоступні налаштування дозволили Web Api встановлювати властивості, але не дозволяли їм їх серіалізувати.
Даніель Саїді

7
У поєднанні з використанням JsonPropertyAttribute, від C # 6.0 ви можете використовувати nameofключове слово замість "магічних рядків". Це робить рефакторинг набагато простішим і безвідмовним - плюс, якщо ви пропустите перейменування будь-яких подій, компілятор все одно попередить вас. На прикладі @ Брайана, використання було б таким:[JsonProperty(nameof(ObsoleteSetting))]
Джефф Джеймс

1
Дуже погано використовувати nameof () в деклараціях JsonProperty, особливо в цьому застарілому сценарії. JSON являє собою зовнішній (і, сподіваємось, вічний) договір з іншим інтерфейсом, і ви точно не хочете змінювати назву властивості JSON, якщо рефактор. Ви порушите сумісність усіх існуючих файлів та компонентів JSON, які генерують JSON у цьому форматі. Насправді, вам краще буде розмістити JsonProperty (…) з повним іменем на кожному серіалізованому ресурсі, щоб переконатися, що вони не змінюються, якщо пізніше перейменуватимете його.
Ammo Goettsch

36

У будь-якій ситуації, коли прийнятне, щоб ваше властивість, призначене лише для десеріалізації, було позначене внутрішнім, існує надзвичайно просте рішення, яке взагалі не залежить від атрибутів. Просто позначте властивість як внутрішнє отримання, але загальнодоступне:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Це призводить до правильної десеріалізації за допомогою налаштувань / розділових значень за замовчуванням, але властивість позбавлена ​​серіалізованого виводу.


Просте, але розумне рішення.
hbulens

Зауважте, що властивість буде ігноровано модулем перевірки. (Отже, ви більше не можете позначати його як [Потрібно] для десеріалізації, оскільки це покладається на публічний getметод).
Мартін Хансен

2
Це не працює ні з, internalні з private. Це завжди серіалізується.
Павло

Це для мене не вийшло. Отримано помилку властивості, яку не знайдено під час десеріалізації.
Дрю Сумідо

31

Мені подобається дотримуватися атрибутів на цьому, ось метод, який я використовую, коли потрібно десеріалізувати властивість, але не серіалізувати його чи навпаки.

КРОК 1 - Створіть спеціальний атрибут

public class JsonIgnoreSerializationAttribute : Attribute { }

КРОК 2 - Створіть користувацьке відновлення контракту

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

КРОК 3 - Додайте атрибут там, де серіалізація не потрібна, але десеріалізація є

    [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

КРОК 4 - Використовуйте його

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

Сподіваюся, це допомагає! Також варто зазначити, що це також буде ігнорувати властивості, коли відбувається десеріалізація, коли я дерсеріалізую, я просто використовую конвертер звичайним способом.

JsonConvert.DeserializeObject<MyType>(myString);

Дякуємо за цю корисну реалізацію. Чи є спосіб розширити базова реалізація, GetSerializableMembersа не перекрити її повністю?
Ален

4
Не забудьте, щойно зрозумів, що це так просто, як:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Ален

не впевнений, чому це не найкраща відповідь. це чисто, дотримується моделей ньютонсофт і це легко зробити. Єдине, що я хотів би додати, це те, що ви можете налаштувати його в усьому світі, використовуючиJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Мт М,

1
незважаючи на це, це насправді не робить того, що просить просити. це в основному відтворює JsonIgnore. він не пропускає властивість для серіалізації, але встановлює властивість під час десеріалізації, тому що метод GetSerializableMembers не може знати, чи є це читання чи запис, тому ви виключаєте властивості для обох. це рішення не працює.
Мет М

7

Використовувати властивість налаштування:

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

Сподіваюся, що це допоможе.


1
Дякую. Зауважте, що властивість JsonProperty повинна мати верхній регістр IgnoreOnSerializing, рівний властивості. Я рекомендую використовувати, nameof(IgnoreOnSerializing)щоб уникнути чарівної стрічки, у разі перейменування.
Бендік Август Несбё,

5

Після того, як я витратив досить довгий час на пошуки того, як позначити властивість класу De-Serializable і NOT Serializable, я виявив, що такого взагалі немає для цього; тож я придумав рішення, яке поєднує дві різні бібліотеки або методи серіалізації (System.Runtime.Serialization.Json & Newtonsoft.Json), і воно працювало для мене так, як наступне:

  • позначте всі свої класи та підкласи як "DataContract".
  • позначте всі властивості вашого класу та підкласів як "DataMember".
  • позначте всі властивості вашого класу та підкласів як "JsonProperty", за винятком тих, для яких ви хочете, щоб вони не були серіалізовані.
  • тепер позначте властивості, які НЕ хочете, щоб вони були серіалізовані як "JsonIgnore".
  • потім серіалізуйте за допомогою "Newtonsoft.Json.JsonConvert.SerializeObject" і де-серіалізуйте за допомогою "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 });
            }
        }
    }
    

Сподіваюся, що це допомагає ...


1
Браво Ахмед Абулазм. спасибі це врятувало мене від багато роботи. :)
Sike12

2

з посиланням на рішення @ ThoHo, використання сетера - це фактично все, що потрібно, без додаткових тегів.

Для мене раніше був єдиний посилання Id, який я хотів завантажити та додати до нової колекції довідкових ідентифікаторів. Змінюючи визначення посилання Id, що містить лише метод сеттера, який додав значення новій колекції. Json не може записати значення назад, якщо властивість не має ; метод.

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

Цей клас тепер сумісний з попередньою версією та зберігає лише RefIds для нових версій.


1

Щоб спиратися на відповідь То Хо, це також можна використовувати для полів.

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

[JsonIgnore]
public string IgnoreOnSerializing;

1

Залежно від того, де в додатку це відбувається, і якщо це лише одна властивість, одним ручним способом ви можете це зробити, встановивши значення властивості на нульове значення, а потім у моделі ви можете вказати, що властивість буде ігноруватися, якщо значення є нульовим:

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

Якщо ви працюєте над веб-додатком ASP.NET Core, ви можете глобально встановити це для всіх властивостей у всіх моделях, встановивши це у файлі Startup.cs:

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

0

Якщо ви використовуєте JsonConvert, IgnoreDataMemberAttribute - це нормально. Моя стандартна бібліотека не посилається на Newton.Json, і я використовую [IgnoreDataMember] для керування серіалізацією об'єктів.

З довідкового документа Newton.net .


0

Чи є якийсь простий спосіб позначити (наприклад, з атрибутами) властивість об'єкта C #, щоб Json.net ігнорував його ТІЛЬКИ при серіалізації, але відвідував його під час десеріалізації?

Найпростіший спосіб, який я знайшов під час написання цього документа, - це включити цю логіку у свій IContractResolver .

Зразок коду зверху, скопійований сюди для нащадків:

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

Усі відповіді хороші, але такий підхід видався найчистішим. Я реально реалізував це, шукаючи атрибут властивості для SkipSerialize та SkipDeserialize, щоб ви могли просто позначити будь-який клас, яким ви керуєте. Чудове запитання!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.