如果我們是真的用物件導向在設計程式,那麼一定會用到抽象類的型別(Abstract Class、Interface),在現今當下的資料交換格式中,JSON 算是大家首選的格式,可是當我們的設計相依於抽象之後,序列化及反序列化就變成一個我們必須特別要處理的點,序列化倒是還好,反序列化就比較頭痛了。
假設我有一個資料模型的設計是這樣子的,Customer
是抽象類別,以客戶的所屬地區來產生 Customer 的派生類別,在 Customer 中定義了一個 List<Order>
是記錄客戶下的訂單,而 Order
本身也是個抽象類別,以產品類別來產生 Order 的派生類別。
我設計了一個 CustomerController
,其中有一個 Add(Customer customer)
的 Action,我們可以看到 Add() 的參數是 Customer,因此 Add() 相依於 Customer 這個抽象類別。
而對於呼叫 Add() 的 Client 端來說,它只要面對 Add(Customer customer) 這個 Api 方法,不必管後端是怎麼處理來自不同地區的 Customer,即使需求要加入一個來自 America 的客戶,Client 端面對的依舊是 Add(Customer customer),不會因為加了來自 America 的客戶,Client 端就需要多呼叫 Add(AmericaCustomer americaCustomer)
Api 方法。
自訂 Customer ModelBinder
但是如果我們不做處理直接就這樣上場,在 Client 端呼叫 Add() 時,反序例化就會出問題。
這時候我們就必須自己做 ModelBinder,建立 CustomerModelBinder
實作 IModelBinder
自己來處理反序列化,而我反序列化的工具是用 Json.NET。
public class CustomerModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
var stream = new StreamReader(controllerContext.RequestContext.HttpContext.Request.InputStream, Encoding.UTF8);
var json = stream.ReadToEnd();
return JsonConvert.DeserializeObject(json, bindingContext.ModelType);
}
}
接著在要使用自定義 ModelBinder 的 Action 參數前加上這一串。
自訂 Customer JsonConverter
我們直接用 JsonConvert.DeserializeObject
將收到的內容直接反序列化,以為這樣就可以了,殊不知我們還差一步。
JsonConvert 不知道我們實際上要反序列化的派生類別是什麼,這個部分我們就必須自己自訂 JsonConverter
來告訴 JsonConvert 我們優先要使用的 JsonConverter 是哪些?
在這個範例裡面,要反序列化的對象有 2 個抽象類別 Customer
、Order
所以我們必須要針對這兩個抽象類別各自做一個 JsonConverter 來對應。
CustomerConverter
public class CustomerConverter : JsonConverter
{
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return
objectType.Equals(typeof(List<Customer>))
|| objectType.Equals(typeof(Customer));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType.Equals(JsonToken.StartArray))
{
return
JArray.Load(reader)
.Cast<JObject>()
.Select(o =>
{
return GenerateCustomerObject(o, serializer);
})
.ToList();
}
if (reader.TokenType.Equals(JsonToken.StartObject))
{
return GenerateCustomerObject(JObject.Load(reader), serializer);
}
return null;
}
private Customer GenerateCustomerObject(JObject jobj, JsonSerializer serializer)
{
switch ((CustomerType)(int)jobj["Type"])
{
case CustomerType.Taiwan: return jobj.ToObject<TaiwanCustomer>(serializer);
case CustomerType.America: return jobj.ToObject<AmericaCustomer>(serializer);
default: return null;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
OrderConverter
public class OrderConverter : JsonConverter
{
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return
objectType.Equals(typeof(List<Order>))
|| objectType.Equals(typeof(Order));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType.Equals(JsonToken.StartArray))
{
return
JArray.Load(reader)
.Cast<JObject>()
.Select(o =>
{
return GenerateOrderObject(o, serializer);
})
.ToList();
}
if (reader.TokenType.Equals(JsonToken.StartObject))
{
return GenerateOrderObject(JObject.Load(reader), serializer);
}
return null;
}
private Order GenerateOrderObject(JObject jobj, JsonSerializer serializer)
{
switch ((OrderType)(int)jobj["Type"])
{
case OrderType.Book: return jobj.ToObject<BookOrder>(serializer);
case OrderType.Car: return jobj.ToObject<CarOrder>(serializer);
default: return null;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
最後在呼叫 JsonConvert.DeserializeObject 的時候,明確地指定我們要使用的 JsonConverter。
加上去之後,JsonConvert 就可以幫我們做明確地轉型了。
參考資料
< Source Code >