Json.NET技巧-反序列化還原為不同型別的集合
2 |
情境如下,我們定義一個抽象型別Notification保存排程發送通知的資料(包含JobType、ScheduleTime及Message),依發送管道分為電子郵件通知及簡訊通知,故實作成EmailNotification及SMSNotification兩個類別,並各自增加Email及PhoneNo屬性。
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
namespace CustCreate
{
public enum Channels
{
Email, SMS
}
//通知作業
public abstract class Notification
{
[JsonConverter(typeof(StringEnumConverter))]
//通知管道
public Channels JobType { get; protected set; }
//排程時間
public DateTime ScheduledTime { get; set; }
//訊息內容
public string Message { get; set; }
protected Notification(DateTime time, string msg)
{
ScheduledTime = time;
Message = msg;
}
protected Notification() { }
}
//電子郵件通知
public class EmailNotification : Notification
{
public string Email { get; set; }
public EmailNotification(DateTime time, string email, string msg)
: base(time, msg)
{
JobType = Channels.Email;
Email = email;
}
}
//簡訊通知
public class SMSNotification : Notification
{
//電話號碼
public string PhoneNo { get; set; }
public SMSNotification()
{
JobType = Channels.SMS;
}
public SMSNotification(DateTime time, string phoneNo, string msg)
: base(time, msg)
{
JobType = Channels.SMS;
PhoneNo = phoneNo;
}
}
}
依循上述資料結構,我們可輕易產生一個List<Notification>,其中包含EmailNotification及SMSNotification兩種不同型別的物件,用JsonConvert.SerializeObject()簡單轉成JSON:
var jobs = new List<Notification>();
jobs.Add(new EmailNotification(
DateTime.UtcNow, "blah@bubu.blah.boo", "Test 1"));
jobs.Add(new SMSNotification(
DateTime.UtcNow, "0912345678", "Test 2"));
Console.WriteLine(
JsonConvert.SerializeObject(jobs, Formatting.Indented));
Console.Read();
產生JSON字串如下:
[ { "Email": "blah@bubu.blah.boo", "JobType": "Email", "ScheduledTime": "2013-12-13T13:43:25.6881876Z", "Message": "Test 1" }, { "PhoneNo": "0912345678", "JobType": "SMS", "ScheduledTime": "2013-12-13T13:43:25.6891803Z", "Message": "Test 2" } ]
到目前為止非常輕鬆愉快吧? 然後呢? 以前學過的JsonConvert.DeserializeObject<T>都只能轉回單一型別,要怎麼把它反序列化還原回內含EmailNotification及SMSNotification不同型別物件的List<Notification>?
雖然不算常見情境,但這可難不倒偉大的Json.NET!
以下是程式範例,關鍵在於我們得繼承CustomCreationConverter<T>自訂一個轉換Notification物件的轉換器作為DesializeObject時的第二個參數。而在轉換器中可透過覆寫ReadJson()方法,依輸入JSON內容,傳回轉換後的物件。如此,我們就能依JobType動態建立不同型別的物件,再從JSON內容取得屬性值,達成反序列化還原出不同型別物件的目標。
另外,稍早定義物件時有預留伏筆,EmailNotification只提供有參數的建構式,而SMSNotification則支援無參數建構式(可沿用內建的屬性對應機制),以便示範兩種不同做法,細節部分請直接看程式碼及註解。
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
namespace CustCreate
{
class Program
{
static string json = @"[
{
""Email"": ""blah@bubu.blah.boo"",
""JobType"": ""Email"",
""ScheduledTime"": ""2013-12-13T13:43:25.6881876Z"",
""Message"": ""Test 1""
},
{
""PhoneNo"": ""0912345678"",
""JobType"": ""SMS"",
""ScheduledTime"": ""2013-12-13T13:43:25.6891803Z"",
""Message"": ""Test 2""
}
]";
//自訂轉換器,繼承CustomCreationConverter<T>
class NotificationConverter : CustomCreationConverter<Notification>
{
//由於ReadJson會依JSON內容建立不同物件,用不到Create()
public override Notification Create(Type objectType)
{
throw new NotImplementedException();
}
//自訂解析JSON傳回物件的邏輯
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
//先取得JobType,由其決定建立物件
string jobType = jo["JobType"].ToString();
if (jobType == "Email")
{
//做法1: 由JObject取出建構式所需參數建構物件
var target = new EmailNotification(
jo["ScheduledTime"].Value<DateTime>(),
jo["Email"].ToString(),
jo["Message"].ToString()
);
return target;
}
else if (jobType == "SMS")
{
//做法2: 若物件支援無參數建構式,則可直接透過
// serializer.Populate()自動對應屬性
var target = new SMSNotification();
serializer.Populate(jo.CreateReader(), target);
return target;
}
else
throw new ApplicationException("Unsupported type: " + jobType);
}
}
static void Main(string[] args)
{
//JsonConvert.DeserializeObject時傳入自訂Converter
var list = JsonConvert.DeserializeObject<List<Notification>>(
json, new NotificationConverter());
var item = list[0];
Console.WriteLine("Type:{0} Email={1}",
item.JobType, (item as EmailNotification).Email);
item = list[1];
Console.WriteLine("Type:{0} PhoneNo={1}",
item.JobType, (item as SMSNotification).PhoneNo);
Console.Read();
}
}
}
就醬,我們就從JSON完整重現原本的List<Notification>囉~
Type:Email Email=blah@bubu.blah.boo Type:SMS PhoneNo=0912345678
Comments
# by Mars
請問如果email也提供無參數建構式是否就兩種type都可以使用SMS那段程式碼就可行了呢?
# by Jeffrey
to Mars, 是的,EmailNotification是刻意設計以展示必須有建構參數時的做法,否則可用SMSNotification的做法較簡單。