Quantcast
Channel: 黑暗執行緒
Viewing all articles
Browse latest Browse all 2311

Json.NET技巧-反序列化還原為不同型別的集合

$
0
0

情境如下,我們定義一個抽象型別Notification保存排程發送通知的資料(包含JobType、ScheduleTime及Message),依發送管道分為電子郵件通知及簡訊通知,故實作成EmailNotification及SMSNotification兩個類別,並各自增加Email及PhoneNo屬性。

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
 
namespace CustCreate
{
publicenum Channels
    {
        Email, SMS
    }
//通知作業
publicabstractclass Notification
    {
        [JsonConverter(typeof(StringEnumConverter))]
//通知管道
public Channels JobType { get; protected set; }
//排程時間
public DateTime ScheduledTime { get; set; }
//訊息內容
publicstring Message { get; set; }
 
protected Notification(DateTime time, string msg)
        {
            ScheduledTime = time;
            Message = msg;
        }
 
protected Notification() { }
    }
//電子郵件通知
publicclass EmailNotification : Notification
    {
publicstring Email { get; set; }
public EmailNotification(DateTime time, string email, string msg)
            : base(time, msg)
        {
            JobType = Channels.Email;
            Email = email;
        }
    }
//簡訊通知
publicclass SMSNotification : Notification
    {
//電話號碼
publicstring 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
    {
staticstring 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()
publicoverride Notification Create(Type objectType)
            {
thrownew NotImplementedException();
            }
//自訂解析JSON傳回物件的邏輯
publicoverrideobject 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;
                }
elseif (jobType == "SMS")
                {
//做法2: 若物件支援無參數建構式,則可直接透過
//       serializer.Populate()自動對應屬性
                    var target = new SMSNotification();
                    serializer.Populate(jo.CreateReader(), target);
return target;
                }
else
thrownew ApplicationException("Unsupported type: " + jobType);
            }
        }
staticvoid 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

Viewing all articles
Browse latest Browse all 2311

Trending Articles