Membuat properti deserialize tetapi tidak membuat serial dengan json.net

124

Kami memiliki beberapa file konfigurasi yang dihasilkan dengan membuat serialisasi objek C # dengan Json.net.

Kami ingin memindahkan satu properti kelas berseri dari properti enum sederhana menjadi properti kelas.

Salah satu cara mudah untuk melakukan ini, akan meninggalkan properti enum lama di kelas, dan mengatur agar Json.net membaca properti ini saat kita memuat konfigurasi, tetapi tidak menyimpannya lagi saat kita menyusun objek berikutnya. Kami akan berurusan dengan menghasilkan kelas baru dari enum lama secara terpisah.

Apakah ada cara sederhana untuk menandai (misalnya dengan atribut) properti dari objek C #, sehingga Json.net akan mengabaikannya HANYA saat membuat serial, tetapi mengatasinya saat melakukan deserialisasi?

Will Dean
sumber
Bagaimana dengan konverter khusus: Anda dapat menggunakannya sebagai atribut pada properti Anda, mengganti ReadJson dan WriteJson dengan perilaku yang berbeda, bukan? Contoh (bukan yang Anda butuhkan, tapi ...) weblogs.asp.net/thangchung/archive/2010/08/26/…
Raphaël Althaus
Atribut OnDeserialized dapat menjadi solusi untuk Anda
Estefany Velez
Bukankah seharusnya itu bisa dilakukan dengan menggunakan atribut `[JsonIgnore] '?? james.newtonking.com/archive/2009/10/23/…
Juri
Apakah Anda dapat menjelaskan bagaimana ini dapat digunakan dalam satu arah saja, sesuai dengan paragraf terakhir dari q?
Will Dean
Dimungkinkan untuk menggunakan [JsonIgnore] dalam kombinasi dengan penyetel pribadi sekunder yang didekorasi dengan atribut [JsonProperty]. Ada beberapa solusi sederhana lainnya juga. Saya telah menambahkan artikel rinci.
Brian Rogers

Jawaban:

133

Sebenarnya ada beberapa pendekatan yang cukup sederhana yang dapat Anda gunakan untuk mencapai hasil yang Anda inginkan.

Mari kita asumsikan, misalnya, bahwa kelas Anda saat ini ditentukan seperti ini:

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

enum Fizz { Alpha, Beta, Gamma }

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

Dan Anda ingin melakukan ini:

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

Untuk mendapatkan ini:

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

Pendekatan 1: Tambahkan metode ShouldSerialize

Json.NET memiliki kemampuan untuk membuat serialisasi properti secara bersyarat dengan mencari ShouldSerializemetode yang sesuai di kelas.

Untuk menggunakan fitur ini, tambahkan ShouldSerializeBlah()metode boolean ke kelas Anda yang Blahakan diganti dengan nama properti yang tidak ingin Anda buat serialnya. Jadikan penerapan metode ini selalu kembali false.

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

    public Bang ReplacementSetting { get; set; }

    public bool ShouldSerializeObsoleteSetting()
    {
        return false;
    }
}

Catatan: jika Anda menyukai pendekatan ini tetapi tidak ingin merusak antarmuka publik kelas Anda dengan memperkenalkan ShouldSerializemetode, Anda dapat menggunakan IContractResolveruntuk melakukan hal yang sama secara terprogram. Lihat Serialisasi Properti Bersyarat dalam dokumentasi.

Pendekatan 2: Memanipulasi JSON dengan JObjects

Alih-alih menggunakan JsonConvert.SerializeObjectuntuk melakukan serialisasi, muat objek config ke a JObject, lalu hapus properti yang tidak diinginkan dari JSON sebelum menulisnya. Itu hanya beberapa baris kode tambahan.

JObject jo = JObject.FromObject(config);

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

json = jo.ToString();

Pendekatan 3: Penggunaan atribut yang cerdik (ab)

  1. Menerapkan [JsonIgnore]atribut ke properti yang Anda tidak ingin menjadi serial.
  2. Tambahkan alternatif, penyetel properti pribadi ke kelas dengan tipe yang sama dengan properti asli. Jadikan implementasi properti itu sebagai properti asli.
  3. Menerapkan [JsonProperty]atribut ke penyetel alternatif, memberinya nama JSON yang sama dengan properti aslinya.

Inilah Configkelas yang direvisi :

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; }
}
Brian Rogers
sumber
7
Kami menyelesaikannya dalam proyek kami (yang menggunakan kumpulan super spesifik integrasi internal dari model dasar, di mana tidak ada properti superclass yang harus diserialisasi), dengan menyetel properti get ke internal. Memiliki penyetel publik mengizinkan Api Web untuk menyetel properti, tetapi menghentikannya dari membuat serialisasi.
Daniel Saidi
7
Sehubungan dengan menggunakan JsonPropertyAttribute, dari C # 6.0 Anda dapat menggunakan nameofkata kunci daripada menggunakan "string ajaib". Hal ini membuat pemfaktoran ulang menjadi jauh lebih mudah dan sangat mudah - plus, jika Anda melewatkan mengganti nama kejadian apa pun, kompilator akan memperingatkan Anda. Menggunakan contoh @ Brian, penggunaannya akan seperti ini:[JsonProperty(nameof(ObsoleteSetting))]
Geoff James
1
Sebaiknya gunakan nameof () di deklarasi JsonProperty, terutama dalam skenario lama ini. JSON mewakili kontrak eksternal (dan mudah-mudahan abadi) dengan antarmuka lain, dan Anda pasti TIDAK ingin mengubah nama properti JSON jika Anda melakukan refaktorisasi. Anda akan merusak kompatibilitas semua file dan komponen JSON yang ada yang menghasilkan JSON dalam format ini. Nyatanya, Anda akan lebih baik menempatkan JsonProperty (…) dengan nama lengkap pada setiap properti berseri untuk memastikan mereka tidak berubah jika Anda kemudian mengganti nama properti.
Amunisi Goettsch
36

Untuk situasi apa pun yang mengizinkan properti khusus deserialisasi Anda ditandai internal, ada solusi yang sangat sederhana yang tidak bergantung pada atribut sama sekali. Cukup tandai properti sebagai get internal, tetapi set publik:

public class JsonTest {

    public string SomeProperty { internal get; set; }

}

Ini menghasilkan deserialisasi yang benar menggunakan pengaturan default / resolver / dll., Tetapi properti dihapus dari output serial.

Iucounu
sumber
Solusi sederhana namun cerdas.
hbulens
Harap diperhatikan bahwa properti tersebut akan diabaikan oleh modul validasi juga. (Jadi Anda tidak dapat lagi menandainya sebagai [Wajib] untuk deserialisasi, karena ini bergantung pada getmetode publik ).
Martin Hansen
2
Ini tidak bekerja dengan internalnor private. Itu selalu berseri.
Paul
Ini tidak berhasil untuk saya. Punya properti tidak ditemukan kesalahan saat deserializing.
Drew Sumido
31

Saya suka menempel dengan atribut yang satu ini, berikut adalah metode yang saya gunakan ketika perlu deserialisasi properti tetapi tidak membuat serial atau sebaliknya.

LANGKAH 1 - Buat atribut khusus

public class JsonIgnoreSerializationAttribute : Attribute { }

LANGKAH 2 - Buat Pengembalian Kontrak kustom

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

LANGKAH 3 - Tambahkan atribut di mana serialisasi tidak diperlukan tetapi deserialisasi diperlukan

    [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

LANGKAH 4 - Gunakan

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

Semoga ini membantu! Juga perlu dicatat bahwa ini juga akan mengabaikan properti ketika Deserialization terjadi, ketika saya melakukan derserialisasi, saya hanya menggunakan konverter dengan cara konvensional.

JsonConvert.DeserializeObject<MyType>(myString);
Jraco11
sumber
Terima kasih atas penerapan yang bermanfaat ini. Apakah ada cara untuk memperluas implementasi dasar GetSerializableMembersdaripada menimpanya seluruhnya?
Alain
4
Nevermind, baru menyadari itu sesederhana:return base.GetSerializableMembers(objectType).Where(pi => !Attribute.IsDefined(pi, typeof(JsonIgnoreSerializationAttribute))).ToList();
Alain
tidak yakin mengapa ini bukan jawaban peringkat teratas. itu bersih, mengikuti pola newtonsoft dan mudah dilakukan. satu-satunya hal yang akan saya tambahkan adalah Anda dapat mengaturnya secara global menggunakanJsonConvert.DefaultSettings = () => new JsonSerializerSettings { ContractResolver = new JsonPropertiesResolver() }
Matt M
1
Tidak masalah, ini tidak benar-benar melakukan apa yang diminta penanya. ini pada dasarnya membuat ulang JsonIgnore. itu tidak melewatkan properti untuk serialisasi tetapi mengatur properti selama deserialization karena tidak ada cara untuk metode GetSerializableMembers untuk mengetahui apakah itu baca atau tulis sehingga Anda mengecualikan properti untuk keduanya. solusi ini tidak berhasil.
Matt M
7

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

Semoga bantuan ini.

Tho Ho
sumber
1
Terima kasih. Perhatikan bahwa JsonProperty harus memiliki huruf besar IgnoreOnSerializing, sama dengan properti. Saya merekomendasikan penggunaan nameof(IgnoreOnSerializing)untuk menghindari string ajaib, dalam kasus penggantian nama.
Bendik Agustus Nesbø
5

Setelah saya menghabiskan waktu yang cukup lama mencari bagaimana menandai properti kelas menjadi De-Serializable dan NOT Serializable saya menemukan bahwa tidak ada hal seperti itu untuk melakukan itu sama sekali; jadi saya datang dengan solusi yang menggabungkan dua pustaka yang berbeda atau teknik serialisasi (System.Runtime.Serialization.Json & Newtonsoft.Json) dan itu bekerja untuk saya seperti berikut:

  • tandai semua kelas dan sub-kelas Anda sebagai "DataContract".
  • tandai semua properti kelas dan sub-kelas Anda sebagai "Anggota Data".
  • tandai semua properti kelas dan sub-kelas Anda sebagai "JsonProperty" kecuali properti yang Anda ingin agar tidak diserialkan.
  • sekarang tandai properti yang Anda TIDAK ingin menjadi serial sebagai "JsonIgnore".
  • kemudian lakukan serialisasi menggunakan "Newtonsoft.Json.JsonConvert.SerializeObject" dan De-Serialize menggunakan "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 });
            }
        }
    }

Semoga membantu ...

Ahmed Abulazm
sumber
1
Bravo Ahmed Abulazm. terima kasih itu menyelamatkan saya dari banyak pekerjaan. :)
Sike12
2

dengan mengacu pada solusi @ ThoHo, penggunaan setter sebenarnya adalah semua yang diperlukan, tanpa tag tambahan.

Bagi saya, saya sebelumnya memiliki satu ID referensi, yang ingin saya muat dan tambahkan ke koleksi ID referensi baru. Dengan mengubah definisi Id referensi agar hanya berisi metode penyetel, yang menambahkan nilai ke koleksi baru. Json tidak dapat menulis kembali nilainya jika Properti tidak memiliki get; metode.

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

Kelas ini sekarang kompatibel dengan versi sebelumnya dan hanya menyimpan Referensi untuk versi baru.

simo.3792
sumber
1

Untuk mengembangkan jawaban Tho Ho, ini juga dapat digunakan untuk ladang.

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

[JsonIgnore]
public string IgnoreOnSerializing;
Bendik Agustus Nesbø
sumber
1

Bergantung di mana dalam aplikasi ini terjadi dan jika itu hanya satu properti, satu cara manual yang dapat Anda lakukan adalah dengan menyetel nilai properti ke null dan kemudian pada model Anda dapat menentukan bahwa properti diabaikan jika nilainya null:

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

Jika Anda bekerja pada aplikasi web ASP.NET Core, Anda dapat mengatur ini secara global untuk semua properti di semua model dengan mengatur ini di file Startup.cs Anda:

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

Jika Anda menggunakan JsonConvert, IgnoreDataMemberAttribute is ok. Pustaka standar saya tidak refence Newton.Json, dan saya menggunakan [IgnoreDataMember] untuk mengontrol serialisasi objek.

Dari dokumen bantuan Newton.net .

menxin
sumber
0

Apakah ada cara sederhana untuk menandai (misalnya dengan atribut) properti dari objek C #, sehingga Json.net akan mengabaikannya HANYA saat membuat serial, tetapi mengatasinya saat melakukan deserialisasi?

Cara termudah yang saya temukan pada tulisan ini adalah dengan memasukkan logika ini ke dalam IContractResolver Anda .

Kode contoh dari tautan di atas disalin di sini untuk anak cucu:

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

Semua jawaban bagus tetapi pendekatan ini sepertinya cara yang paling bersih. Saya benar-benar menerapkan ini dengan mencari atribut pada properti untuk SkipSerialize dan SkipDeserialize sehingga Anda dapat menandai kelas apa pun yang Anda kontrol. Pertanyaan bagus!

Tidak Ada Pengembalian Dana Tidak Ada Pengembalian
sumber