Menggunakan konverter Json.NET untuk deserialisasi properti


88

Saya memiliki definisi kelas yang berisi properti yang mengembalikan antarmuka.

public class Foo
{ 
    public int Number { get; set; }

    public ISomething Thing { get; set; }
}

Mencoba untuk membuat serial kelas Foo menggunakan Json.NET memberi saya pesan kesalahan seperti, "Tidak dapat membuat contoh jenis 'ISomething'. ISomething mungkin sebuah antarmuka atau kelas abstrak."

Apakah ada atribut atau konverter Json.NET yang memungkinkan saya menentukan Somethingkelas konkret untuk digunakan selama deserialisasi?


Saya yakin Anda perlu menentukan nama properti yang mendapat / menyetel ISomething
ram

Saya sudah. Saya menggunakan singkatan untuk properti yang diimplementasikan secara otomatis yang diperkenalkan di C # 3.5. msdn.microsoft.com/en-us/library/bb384054.aspx
dthrasher

4
Bukankah itu adalah tipe. Saya pikir ram benar, Anda masih membutuhkan nama properti. Saya tahu ini tidak terkait dengan masalah Anda, tetapi komentar Anda di atas membuat saya berpikir saya kehilangan beberapa fitur baru di .NET yang memungkinkan Anda menentukan properti tanpa nama.
Tuan Moose

Jawaban:


92

Salah satu hal yang dapat Anda lakukan dengan Json.NET adalah:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;

JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

The TypeNameHandlingbendera akan menambahkan $typeproperti ke JSON, yang memungkinkan Json.NET untuk mengetahui jenis beton perlu deserialize objek ke dalam. Ini memungkinkan Anda untuk melakukan deserialisasi objek sambil tetap memenuhi antarmuka atau kelas dasar abstrak.

Sisi negatifnya, bagaimanapun, adalah bahwa ini sangat spesifik Json.NET. Ini $typeakan menjadi tipe yang sepenuhnya memenuhi syarat, jadi jika Anda membuat serial dengan info tipe ,, deserializer harus bisa memahaminya juga.

Dokumentasi: Pengaturan Serialisasi dengan Json.NET


Menarik. Saya harus bermain-main dengan ini. Tip yang bagus!
dthrasher

2
Untuk Newtonsoft.Json cara kerjanya serupa, tetapi propertinya adalah "$ type"
Jaap

Itu terlalu mudah!
Shimmy Weitzhandler

1
Waspadai kemungkinan masalah keamanan di sini saat menggunakan TypeNameHandling. Lihat hati-hati TypeNameHandling di Newtonsoft Json untuk detailnya.
dbc

Saya berjuang keras dengan konverter kemarin, dan ini jauh lebih baik dan lebih dapat dimengerti, terima kasih !!!
Horothenic

52

Anda dapat mencapai ini melalui penggunaan kelas JsonConverter. Misalkan Anda memiliki kelas dengan properti antarmuka;

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(TycoonConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

JsonConverter Anda bertanggung jawab untuk membuat serial dan membatalkan serialisasi properti yang mendasarinya;

public class TycoonConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    return serializer.Deserialize<Tycoon>(reader);
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

Saat Anda bekerja dengan Organisasi yang dideserialisasi melalui Json.Net, orang yang mendasari untuk properti Pemilik adalah dari tipe Tycoon.


Sangat bagus. Saya harus mencoba konverternya.
dthrasher

4
Akankah tag "[JsonConverter (typeof (TycoonConverter))]" masih berfungsi jika ada di daftar antarmuka?
Zwik

40

Alih-alih meneruskan objek JsonSerializerSettings yang disesuaikan ke JsonConvert.SerializeObject () dengan opsi TypeNameHandling.Objects, seperti yang disebutkan sebelumnya, Anda cukup menandai properti antarmuka tertentu dengan atribut sehingga JSON yang dihasilkan tidak akan membengkak dengan properti "$ type" pada SETIAP objek:

public class Foo
{
    public int Number { get; set; }

    // Add "$type" property containing type info of concrete class.
    [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )]
    public ISomething { get; set; }
}

Cemerlang. Terima kasih :)
Darren Young

5
Untuk koleksi antarmuka atau kelas abstrak, propertinya adalah "ItemTypeNameHandling". contoh: [JsonProperty (ItemTypeNameHandling = TypeNameHandling.Auto)]
Anthony F

Terima kasih untuk ini!
brudert

24

Dalam versi terbaru dari konverter Newtonsoft Json pihak ketiga Anda dapat mengatur konstruktor dengan tipe konkret yang berhubungan dengan properti antarmuka.

public class Foo
{ 
    public int Number { get; private set; }

    public ISomething IsSomething { get; private set; }

    public Foo(int number, Something concreteType)
    {
        Number = number;
        IsSomething = concreteType;
    }
}

Selama Sesuatu mengimplementasikan IS, sesuatu ini harus bekerja. Juga jangan letakkan konstruktor kosong default jika konverter JSon mencoba menggunakan itu, Anda harus memaksanya untuk menggunakan konstruktor yang berisi tipe beton.

PS. ini juga memungkinkan Anda untuk menjadikan penyetel Anda pribadi.


6
Ini harus diteriakkan dari atap! Benar, ini menambahkan kendala pada implementasi konkret, tetapi jauh lebih mudah daripada pendekatan lain untuk situasi di mana ia dapat digunakan.
Mark Meuer

3
Bagaimana jika kita memiliki lebih dari 1 konstruktor dengan beberapa jenis beton, apakah masih akan tahu?
Teoman shipahi

1
Jawaban ini sangat elegan dibandingkan dengan semua omong kosong yang harus Anda lakukan sebaliknya. Ini harus menjadi jawaban yang diterima. Satu peringatan dalam kasus saya, adalah saya harus menambahkan [JsonConstructor] sebelum konstruktor agar berfungsi .... Saya menduga bahwa menggunakan ini hanya pada SALAH SATU konstruktor beton Anda akan menyelesaikan masalah Anda (berusia 4 tahun) @Teomanshipahi
nacitar sevaht

@nacitarsevaht Saya dapat kembali dan memperbaiki masalah saya sekarang :) lagi pula saya bahkan tidak ingat apa itu, tetapi ketika saya melihat kembali, ini adalah solusi yang baik untuk kasus-kasus tertentu.
Teoman shipahi

kami menggunakan ini juga tetapi saya lebih suka mengkonversi dalam banyak kasus karena menggabungkan tipe beton ke konstruktor mengalahkan tujuan menggunakan antarmuka untuk properti di tempat pertama!
gabe

19

Punya masalah yang sama jadi saya datang dengan Konverter saya sendiri yang menggunakan argumen tipe yang dikenal.

public class JsonKnownTypeConverter : JsonConverter
{
    public IEnumerable<Type> KnownTypes { get; set; }

    public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
    {
        KnownTypes = knownTypes;
    }

    protected object Create(Type objectType, JObject jObject)
    {
        if (jObject["$type"] != null)
        {
            string typeName = jObject["$type"].ToString();
            return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+",")));
        }

        throw new InvalidOperationException("No supported type");
    }

    public override bool CanConvert(Type objectType)
    {
        if (KnownTypes == null)
            return false;

        return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);
        // Create target object based on JObject
        var target = Create(objectType, jObject);
        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Saya mendefinisikan dua metode ekstensi untuk deserialisasi dan serialisasi:

public static class AltiJsonSerializer
{
    public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null)
    {
        if (string.IsNullOrEmpty(jsonString))
            return default(T);

        return JsonConvert.DeserializeObject<T>(jsonString,
                new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.Auto, 
                    Converters = new List<JsonConverter>
                        (
                            new JsonConverter[]
                            {
                                new JsonKnownTypeConverter(knownTypes)
                            }
                        )
                }
            );
    }

    public static string SerializeJson(this object objectToSerialize)
    {
        return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented,
        new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
    }
}

Anda dapat menentukan cara Anda sendiri untuk membandingkan dan mengidentifikasi tipe dalam konversi, saya hanya menggunakan nama kelas.


1
JsonConverter ini hebat, saya menggunakannya tetapi menghadapi beberapa masalah yang saya selesaikan dengan cara ini: - Menggunakan JsonSerializer.CreateDefault () alih-alih Mengisi, karena objek saya memiliki hierarki yang lebih dalam. - Menggunakan refleksi untuk mengambil konstruktor dan Instanciate dalam metode Create ()
Aurel

3

Biasanya saya selalu menggunakan solusi dengan TypeNameHandlingseperti yang disarankan oleh DanielT, tetapi dalam kasus di sini saya tidak memiliki kendali atas JSON yang masuk (dan karenanya tidak dapat memastikan bahwa itu termasuk $typeproperti) Saya telah menulis konverter khusus yang hanya memungkinkan Anda untuk menentukan secara eksplisit jenis beton:

public class Model
{
    [JsonConverter(typeof(ConcreteTypeConverter<Something>))]
    public ISomething TheThing { get; set; }
}

Ini hanya menggunakan implementasi serializer default dari Json.Net sementara secara eksplisit menentukan tipe konkret.

Kode sumber dan ikhtisar tersedia di posting blog ini .


1
Ini adalah solusi yang bagus. Bersulang.
JohnMetta

2

Saya hanya ingin melengkapi contoh yang @Daniel T. tunjukkan di atas:

Jika Anda menggunakan kode ini untuk membuat serial objek Anda:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

Kode untuk deserialisasi json akan terlihat seperti ini:

var settings = new JsonSerializerSettings(); 
settings.TypeNameHandling = TypeNameHandling.Objects;
var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);

Beginilah cara json menyesuaikan diri saat menggunakan TypeNameHandlingbendera:masukkan deskripsi gambar di sini


-5

Saya bertanya-tanya tentang hal yang sama ini, tetapi saya khawatir itu tidak dapat dilakukan.

Mari kita lihat seperti ini. Anda menyerahkan ke JSon.net serangkaian data, dan jenis untuk deserialisasi. Apa yang harus dilakukan JSON.net ketika menemukan ISomething itu? Ia tidak dapat membuat tipe baru dari ISsesuatu karena ISomething bukanlah sebuah objek. Ia juga tidak dapat membuat objek yang mengimplementasikan ISomething, karena ia tidak memiliki petunjuk yang mana dari banyak objek yang mungkin mewarisi ISsesuatu yang harus digunakan. Antarmuka, adalah sesuatu yang dapat diserialkan secara otomatis, tetapi tidak otomatis dinonaktifkan.

Apa yang akan saya lakukan adalah melihat menggantikan ISsesuatu dengan kelas dasar. Menggunakan itu Anda mungkin bisa mendapatkan efek yang Anda cari.


1
Saya menyadari itu tidak akan berhasil "di luar kotak". Tapi saya bertanya-tanya apakah ada beberapa atribut seperti "[JsonProperty (typeof (SomethingBase))]" yang dapat saya gunakan untuk menyediakan kelas konkret.
dthrasher

Jadi mengapa tidak menggunakan SomethingBase daripada ISomething dalam kode di atas? Dapat dikatakan bahwa kami juga melihat ini dengan cara yang salah karena Antarmuka tidak boleh digunakan dalam serialisasi, karena mereka hanya mendefinisikan "antarmuka" komunikasi dengan kelas tertentu. Serialisasi antarmuka secara teknis tidak masuk akal, seperti serialisasi kelas abstrak. Jadi, sementara itu "bisa dilakukan", saya berpendapat bahwa itu "tidak boleh dilakukan".
Timothy Baldridge

Pernahkah Anda melihat salah satu kelas di Newtonsoft.Json.Serialization Namespace? khususnya kelas JsonObjectContract?
johnny

-9

Berikut ini referensi ke artikel yang ditulis oleh ScottGu

Berdasarkan itu, saya menulis beberapa kode yang menurut saya mungkin bisa membantu

public interface IEducationalInstitute
{
    string Name
    {
        get; set;
    }

}

public class School : IEducationalInstitute
{
    private string name;
    #region IEducationalInstitute Members

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    #endregion
}

public class Student 
{
    public IEducationalInstitute LocalSchool { get; set; }

    public int ID { get; set; }
}

public static class JSONHelper
{
    public static string ToJSON(this object obj)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        return serializer.Serialize(obj);
    }
    public  static string ToJSON(this object obj, int depth)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RecursionLimit = depth;
        return serializer.Serialize(obj);
    }
}

Dan begitulah Anda menyebutnya

School myFavSchool = new School() { Name = "JFK High School" };
Student sam = new Student()
{
    ID = 1,
    LocalSchool = myFavSchool
};
string jSONstring = sam.ToJSON();

Console.WriteLine(jSONstring);
//Result {"LocalSchool":{"Name":"JFK High School"},"ID":1}

Jika saya memahaminya dengan benar, saya rasa Anda tidak perlu menentukan kelas konkret yang mengimplementasikan antarmuka untuk serialisasi JSON.


1
Sampel Anda menggunakan JavaScriptSerializer, kelas di .NET Framework. Saya menggunakan Json.NET sebagai serializer saya. codeplex.com/Json
dthrasher

3
Tidak mengacu pada pertanyaan awal, Json.NET secara eksplisit disebutkan di sana.
Oliver
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.