Bagikan melalui


Serialisasi JSON dan XML di ASP.NET Web API

Artikel ini menjelaskan pemformat JSON dan XML di ASP.NET Web API.

Dalam ASP.NET Web API, pemformat jenis media adalah objek yang dapat:

  • Membaca objek CLR dari isi pesan HTTP
  • Menulis objek CLR ke dalam isi pesan HTTP

API Web menyediakan pemformat jenis media untuk JSON dan XML. Kerangka kerja menyisipkan pemformat ini ke dalam alur secara default. Klien dapat meminta JSON atau XML di header Terima permintaan HTTP.

Konten

JSON Media-Type Formatter

Pemformatan JSON disediakan oleh kelas JsonMediaTypeFormatter . Secara default, JsonMediaTypeFormatter menggunakan pustaka Json.NET untuk melakukan serialisasi. Json.NET adalah proyek sumber terbuka pihak ketiga.

Jika mau, Anda dapat mengonfigurasi kelas JsonMediaTypeFormatter untuk menggunakan DataContractJsonSerializer alih-alih Json.NET. Untuk melakukannya, atur properti UseDataContractJsonSerializer ke true:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;

Serialisasi JSON

Bagian ini menjelaskan beberapa perilaku spesifik dari pemformat JSON, menggunakan Json.NET serializer default. Ini tidak dimaksudkan untuk menjadi dokumentasi komprehensif pustaka Json.NET; untuk informasi selengkapnya, lihat Dokumentasi Json.NET.

Apa yang Akan Diserialisasikan?

Secara default, semua properti dan bidang publik disertakan dalam JSON berseri. Untuk menghilangkan properti atau bidang, hiasi dengan atribut JsonIgnore .

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    [JsonIgnore]
    public int ProductCode { get; set; } // omitted
}

Jika Anda lebih suka pendekatan "ikut serta", hiasi kelas dengan atribut DataContract . Jika atribut ini ada, anggota diabaikan kecuali mereka memiliki DataMember. Anda juga dapat menggunakan DataMember untuk membuat serialisasi anggota privat.

[DataContract]
public class Product
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public decimal Price { get; set; }
    public int ProductCode { get; set; }  // omitted by default
}

Properti Read-Only

Properti baca-saja diserialisasikan secara default.

Tanggal

Secara default, Json.NET menulis tanggal dalam format ISO 8601 . Tanggal dalam UTC (Waktu Universal Terkoordinasi) ditulis dengan akhiran "Z". Tanggal dalam waktu lokal mencakup offset zona waktu. Contohnya:

2012-07-27T18:51:45.53403Z         // UTC
2012-07-27T11:51:45.53403-07:00    // Local

Secara default, Json.NET mempertahankan zona waktu. Anda dapat mengambil alih ini dengan mengatur properti DateTimeZoneHandling:

// Convert all dates to UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;

Jika Anda lebih suka menggunakan format tanggal Microsoft JSON ("\/Date(ticks)\/") alih-alih ISO 8601, atur properti DateFormatHandling pada pengaturan serializer:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling 
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;

Indentasi

Untuk menulis JSON yang diindentasi, atur pengaturan Pemformatan ke Pemformatan.Diindentasi:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

Camel Casing

Untuk menulis nama properti JSON dengan casing camel, tanpa mengubah model data Anda, atur CamelCasePropertyNamesContractResolver pada serializer:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

Objek Anonim dan Weakly-Typed

Metode tindakan dapat mengembalikan objek anonim dan menserialisasikannya ke JSON. Contohnya:

public object Get()
{
    return new { 
        Name = "Alice", 
        Age = 23, 
        Pets = new List<string> { "Fido", "Polly", "Spot" } 
    };
}

Isi pesan respons akan berisi JSON berikut:

{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}

Jika API web Anda menerima objek JSON yang terstruktur secara longgar dari klien, Anda dapat mendeserialisasi isi permintaan ke jenis Newtonsoft.Json.Linq.JObject .

public void Post(JObject person)
{
    string name = person["Name"].ToString();
    int age = person["Age"].ToObject<int>();
}

Namun, biasanya lebih baik menggunakan objek data yang ditik dengan kuat. Kemudian Anda tidak perlu mengurai data sendiri, dan Anda mendapatkan manfaat validasi model.

Serializer XML tidak mendukung jenis anonim atau instans JObject . Jika Anda menggunakan fitur ini untuk data JSON, Anda harus menghapus pemformat XML dari alur, seperti yang dijelaskan nanti di artikel ini.

XML Media-Type Formatter

Pemformatan XML disediakan oleh kelas XmlMediaTypeFormatter . Secara default, XmlMediaTypeFormatter menggunakan kelas DataContractSerializer untuk melakukan serialisasi.

Jika mau, Anda dapat mengonfigurasi XmlMediaTypeFormatter untuk menggunakan XmlSerializer alih-alihDataContractSerializer. Untuk melakukannya, atur properti UseXmlSerializer ke true:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;

Kelas XmlSerializer mendukung sekumpulan jenis yang lebih sempit daripada DataContractSerializer, tetapi memberikan lebih banyak kontrol atas XML yang dihasilkan. Pertimbangkan untuk menggunakan XmlSerializer jika Anda perlu mencocokkan skema XML yang ada.

Serialisasi XML

Bagian ini menjelaskan beberapa perilaku spesifik pemformat XML, menggunakan DataContractSerializer default.

Secara default, DataContractSerializer berprilaku sebagai berikut:

  • Semua properti dan bidang baca/tulis publik diserialisasikan. Untuk menghilangkan properti atau bidang, hiasi dengan atribut IgnoreDataMember .
  • Anggota privat dan terlindungi tidak diserialisasikan.
  • Properti baca-saja tidak diserialisasikan. (Namun, konten properti koleksi baca-saja diserialisasikan.)
  • Nama kelas dan anggota ditulis dalam XML persis seperti yang muncul dalam deklarasi kelas.
  • Namespace XML default digunakan.

Jika Anda memerlukan kontrol lebih besar atas serialisasi, Anda dapat menghias kelas dengan atribut DataContract . Ketika atribut ini ada, kelas diserialisasikan sebagai berikut:

  • Pendekatan "Ikut" : Properti dan bidang tidak diserialisasikan secara default. Untuk membuat serial properti atau bidang, hiasi dengan atribut DataMember .
  • Untuk membuat serialisasi anggota privat atau terlindungi, hiasi dengan atribut DataMember .
  • Properti baca-saja tidak diserialisasikan.
  • Untuk mengubah bagaimana nama kelas muncul di XML, atur parameter Nama di atribut DataContract .
  • Untuk mengubah bagaimana nama anggota muncul di XML, atur parameter Nama di atribut DataMember .
  • Untuk mengubah namespace XML, atur parameter Namespace di kelas DataContract .

Properti Read-Only

Properti baca-saja tidak diserialisasikan. Jika properti baca-saja memiliki bidang privat yang mendukung, Anda dapat menandai bidang privat dengan atribut DataMember . Pendekatan ini memerlukan atribut DataContract pada kelas .

[DataContract]
public class Product
{
    [DataMember]
    private int pcode;  // serialized

    // Not serialized (read-only)
    public int ProductCode { get { return pcode; } }
}

Tanggal

Tanggal ditulis dalam format ISO 8601. Misalnya, "2012-05-23T20:21:37.9116538Z".

Indentasi

Untuk menulis XML yang diindentasi, atur properti Inden ke true:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;

Mengatur Per-Type Serializer XML

Anda dapat mengatur serializer XML yang berbeda untuk jenis CLR yang berbeda. Misalnya, Anda mungkin memiliki objek data tertentu yang memerlukan XmlSerializer untuk kompatibilitas mundur. Anda dapat menggunakan XmlSerializer untuk objek ini dan terus menggunakan DataContractSerializer untuk jenis lain.

Untuk mengatur serializer XML untuk jenis tertentu, panggil SetSerializer.

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));

Anda dapat menentukan XmlSerializer atau objek apa pun yang berasal dari XmlObjectSerializer.

Menghapus JSON atau Xml Formatter

Anda dapat menghapus formatter JSON atau pemformat XML dari daftar pemformat, jika Anda tidak ingin menggunakannya. Alasan utama untuk melakukan ini adalah:

  • Untuk membatasi respons API web Anda ke jenis media tertentu. Misalnya, Anda mungkin memutuskan untuk hanya mendukung respons JSON, dan menghapus formatter XML.
  • Untuk mengganti formatter default dengan pemformat kustom. Misalnya, Anda dapat mengganti formatter JSON dengan implementasi kustom anda sendiri dari formatter JSON.

Kode berikut menunjukkan cara menghapus pemformat default. Panggil ini dari metode Application_Start Anda, yang ditentukan dalam Global.asax.

void ConfigureApi(HttpConfiguration config)
{
    // Remove the JSON formatter
    config.Formatters.Remove(config.Formatters.JsonFormatter);

    // or

    // Remove the XML formatter
    config.Formatters.Remove(config.Formatters.XmlFormatter);
}

Menangani Referensi Objek Melingkar

Secara default, formatter JSON dan XML menulis semua objek sebagai nilai. Jika dua properti merujuk ke objek yang sama, atau jika objek yang sama muncul dua kali dalam koleksi, pemformat akan membuat serialisasi objek dua kali. Ini adalah masalah khusus jika grafik objek Anda berisi siklus, karena serializer akan memberikan pengecualian saat mendeteksi perulangan dalam grafik.

Pertimbangkan model dan pengontrol objek berikut.

public class Employee
{
    public string Name { get; set; }
    public Department Department { get; set; }
}

public class Department
{
    public string Name { get; set; }
    public Employee Manager { get; set; }
}

public class DepartmentsController : ApiController
{
    public Department Get(int id)
    {
        Department sales = new Department() { Name = "Sales" };
        Employee alice = new Employee() { Name = "Alice", Department = sales };
        sales.Manager = alice;
        return sales;
    }
}

Memanggil tindakan ini akan menyebabkan pemformat melemparkan pengecualian, yang diterjemahkan ke respons kode status 500 (Kesalahan Server Internal) ke klien.

Untuk mempertahankan referensi objek di JSON, tambahkan kode berikut ke metode Application_Start dalam file Global.asax:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.All;

Sekarang tindakan pengontrol akan mengembalikan JSON yang terlihat seperti ini:

{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}

Perhatikan bahwa serializer menambahkan properti "$id" ke kedua objek. Selain itu, ini mendeteksi bahwa properti Employee.Department membuat perulangan, sehingga mengganti nilai dengan referensi objek: {"$ref":"1"}.

Catatan

Referensi objek tidak standar dalam JSON. Sebelum menggunakan fitur ini, pertimbangkan apakah klien Anda akan dapat mengurai hasilnya. Mungkin lebih baik hanya menghapus siklus dari grafik. Misalnya, tautan dari Karyawan kembali ke Departemen tidak benar-benar diperlukan dalam contoh ini.

Untuk mempertahankan referensi objek di XML, Anda memiliki dua opsi. Opsi yang lebih sederhana adalah menambahkan [DataContract(IsReference=true)] ke kelas model Anda. Parameter IsReference memungkinkan referensi objek. Ingatlah bahwa DataContract membuat keikutsertaan serialisasi, jadi Anda juga perlu menambahkan atribut DataMember ke properti:

[DataContract(IsReference=true)]
public class Department
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public Employee Manager { get; set; }
}

Sekarang pemformat akan menghasilkan XML yang mirip dengan berikut ini:

<Department xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="i1" 
            xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" 
            xmlns="http://schemas.datacontract.org/2004/07/Models">
  <Manager>
    <Department z:Ref="i1" />
    <Name>Alice</Name>
  </Manager>
  <Name>Sales</Name>
</Department>

Jika Anda ingin menghindari atribut pada kelas model Anda, ada opsi lain: Buat instans DataContractSerializer khusus jenis baru dan atur preserveObjectReferences ke true di konstruktor. Kemudian atur instans ini sebagai serializer per jenis pada formatter jenis media XML. Kode berikut menunjukkan cara melakukan ini:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);

Menguji Serialisasi Objek

Saat Anda merancang API web, berguna untuk menguji bagaimana objek data Anda akan diserialisasikan. Anda dapat melakukan ini tanpa membuat pengontrol atau memanggil tindakan pengontrol.

string Serialize<T>(MediaTypeFormatter formatter, T value)
{
    // Create a dummy HTTP Content.
    Stream stream = new MemoryStream();
    var content = new StreamContent(stream);
    /// Serialize the object.
    formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
    // Read the serialized string.
    stream.Position = 0;
    return content.ReadAsStringAsync().Result;
}

T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
{
    // Write the serialized string to a memory stream.
    Stream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(str);
    writer.Flush();
    stream.Position = 0;
    // Deserialize to an object of type T
    return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
}

// Example of use
void TestSerialization()
{
    var value = new Person() { Name = "Alice", Age = 23 };

    var xml = new XmlMediaTypeFormatter();
    string str = Serialize(xml, value);

    var json = new JsonMediaTypeFormatter();
    str = Serialize(json, value);

    // Round trip
    Person person2 = Deserialize<Person>(json, str);
}