Создание десериализации свойства, но не сериализации с помощью json.net

124

У нас есть несколько файлов конфигурации, которые были созданы путем сериализации объектов C # с помощью Json.net.

Мы хотели бы перенести одно свойство сериализованного класса из простого свойства перечисления в свойство класса.

Один из простых способов сделать это - оставить в классе старое свойство enum и организовать для Json.net чтение этого свойства при загрузке конфигурации, но не сохранение его снова при следующей сериализации объекта. Мы рассмотрим создание нового класса из старого перечисления отдельно.

Есть ли простой способ пометить (например, с помощью атрибутов) свойство объекта C #, чтобы Json.net игнорировал его ТОЛЬКО при сериализации, но обращал внимание на него при десериализации?

Уилл Дин
источник
А как насчет настраиваемого конвертера: вы можете использовать его в качестве атрибута своего свойства, переопределить ReadJson и WriteJson с другими настройками, нет? Пример (не совсем то, что вам нужно, но ...) weblogs.asp.net/thangchung/archive/2010/08/26/…
Raphaël Althaus
Атрибут 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для сериализации загрузите объект конфигурации в, а 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 значение internal. Наличие общедоступных сеттеров позволяло Web Api устанавливать свойства, но не позволяло им сериализовать их.
Daniel Saidi
7
В сочетании с использованием JsonPropertyAttribute, начиная с C # 6.0, вы можете использовать nameofключевое слово вместо использования «волшебных строк». Это делает рефакторинг намного проще и надежнее - плюс, если вы пропустите переименование каких-либо вхождений, компилятор все равно вас предупредит. Используя пример @Brian, можно использовать следующее:[JsonProperty(nameof(ObsoleteSetting))]
Джефф Джеймс
1
Плохая идея использовать nameof () в объявлениях JsonProperty, особенно в этом устаревшем сценарии. JSON представляет собой внешний (и, надеюсь, вечный) контракт с другим интерфейсом, и вы определенно НЕ хотите изменять имя свойства JSON при рефакторинге. Вы нарушите совместимость со всеми существующими файлами JSON и компонентами, которые генерируют JSON в этом формате. Фактически, вам лучше поставить JsonProperty (…) с полным именем на каждое сериализованное свойство, чтобы они не изменились, если вы позже переименуете свойство.
Ammo Goettsch
36

Для любой ситуации, когда допустимо, чтобы ваше свойство только для десериализации было помечено как внутреннее, существует удивительно простое решение, которое вообще не зависит от атрибутов. Просто отметьте свойство как внутреннее получение, но как открытый набор:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Это приводит к правильной десериализации с использованием параметров по умолчанию / преобразователей / и т. Д., Но свойство удаляется из сериализованного вывода.

Iucounu
источник
Простое, но продуманное решение.
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);
Jraco11
источник
Спасибо за эту полезную реализацию. Есть ли способ расширить базовую реализацию, GetSerializableMembersа не полностью ее заменить?
Ален
4
Неважно, просто понял, что это очень просто:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Ален
не уверен, почему это не самый популярный ответ. он чистый, следует шаблонам newtonsoft и прост в использовании. Единственное, что я хотел бы добавить, это то, что вы можете настроить его глобально, используяJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Мэтт М.
1
неважно, на самом деле это не соответствует тому, о чем просит спрашивающий. это в основном воссоздание JsonIgnore. он не пропускает свойство для сериализации, а устанавливает свойство во время десериализации, потому что метод GetSerializableMembers не может узнать, чтение или запись, поэтому вы исключаете свойства для обоих. это решение не работает.
Matt M
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, а НЕ 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, использование установщика - это на самом деле все, что нужно, без дополнительных тегов.

Для меня раньше у меня был единственный идентификатор ссылки, который я хотел загрузить и добавить в новую коллекцию идентификаторов ссылок. Изменяя определение идентификатора ссылки, чтобы он содержал только метод установки, который добавлял значение в новую коллекцию. Json не может записать значение обратно, если свойство не имеет значения get; метод.

// 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 для новых версий.

simo.3792
источник
1

Чтобы основываться на ответе Тхо Хо, это также можно использовать для полей.

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

[JsonIgnore]
public string IgnoreOnSerializing;
Бендик Аугуст Несбо
источник
1

В зависимости от того, где это происходит в приложении, и если это всего лишь одно свойство, вы можете сделать это вручную , установив значение свойства равным null, а затем в модели вы можете указать, что свойство игнорируется, если значение равно null:

[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);
}
OzzyTheGiant
источник
0

Если вы используете JsonConvert, IgnoreDataMemberAttribute в порядке. Моя стандартная библиотека не ссылается на Newton.Json, и я использую [IgnoreDataMember] для управления сериализацией объекта.

Из справочного документа Newton.net .

menxin
источник
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, чтобы вы могли просто пометить любой контролируемый вами класс. Отличный вопрос!

Нет возврата, нет возврата
источник