2016年8月24日 星期三

json.net 反序列化繼承的子類別問題

今天遇到 json.net 反序列化的問題,
( json.net deserilize dirven class problem! )

前情提要:

開發 web, server 與 client 透過 json 格式傳遞資料,
server 端在定義 ViewMode 時,DataModel 可能是 ChildAModel 或 ChildBModel 類別,
以 OO多型 ( Polymorphism ) 的設計來說,
實作一個父類 ( BaseModel ) 讓 ChildAModel, ChildBModel 繼承,
這樣 ViewModel.DataModel 就可以指定 ChildAModel, ChildBModel 其中之一的資料格式
如下:
public class BaseModel
{
 public int key { get; set; }
}

public class ChildAModel: BaseModel
{
 public string Name { get; set; }
 public int Age { get; set; }
}

public class ChildBModel : BaseModel
{
 public string SomeProperty1 { get; set; }
 public int SomeProperty2 { get; set; }
}

public class ViewModel
{
 public BaseModel DataModel { get; set; }
}

問題:

依這樣的設計,
將 ViewModel 的資料轉成 JSON 拋給 Client 沒問題,
JavaScript 本身就是延展性高的語言,

但由 Client 再拋給 Server 時,
Json.Net 將 JSON 反序列成物件時,
問題來了!
ViewModel.DataModel 只會被反序列化成 BaseModel ,
其他資料全掉光了,
傷腦筋啦! 該如何是好呢?

Solution:

基本概念是靠實作 CustomCreationConverter.Create 來通知 Json.Net 反序列化 T 時,
要轉成哪一種類別,但此 Create 原生方法卻沒有提供其他資訊來判斷該用哪種類別
example:
public interface IPerson
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DateTime BirthDate { get; set; }
}

public class Employee : IPerson
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }

    public string Department { get; set; }
    public string JobTitle { get; set; }
}

public class PersonConverter : CustomCreationConverter
{
    public override IPerson Create(Type objectType)
    {
        return new Employee();
    }
}

發現有善心人士也遇到此問題,並公開解法
覆寫 JsonConverter,擴充 Create 方法
如下: ( 參考 )
public abstract class JsonCreationConverter : JsonConverter
{
 protected abstract T Create(Type objectType, JObject jsonObject);

 public override bool CanConvert(Type objectType)
 {
  return typeof(T).IsAssignableFrom(objectType);
 }

 public override object ReadJson(JsonReader reader, Type objectType, 
  object existingValue, JsonSerializer serializer)
 {
  var jsonObject = JObject.Load(reader);
  var target = Create(objectType, jsonObject);
  serializer.Populate(jsonObject.CreateReader(), target);
  return target;
 }

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

再自訂 Json.Net Conveter,並依物件的 property 回覆類別,如下

public class ModelConverter : JsonCreationConverter
{
 protected override BaseModel Create(Type objectType, 
  JObject jsonObject)
 {
  if (jsonObject["Name"] != null)
  {
   return new ChildAModel();
  }
  else if (jsonObject["SomeProperty1"] != null)
  {
   return new ChildBModel();
  }
  else
  {
   return new BaseModel();
  }
 }
}

最後反序列化的使用方式,如下:

var deserializedModel = JsonConvert.DeserializeObject<BaseModel>(json,
  new ModelConverter ());

以上!

2016年8月3日 星期三

Firefox error: Unable to check input because the pattern is not a valid regexp: invalid identity escape in regular expression


在下大部分時間是用 chrome 開發網頁,
但身為網頁開發人員,
三不五時就要看看自已寫的東西在別的瀏覽器會不會有問題,
今天在 firefox 就中招了,
使用 input pattern attribute 時,
下 regular expression 習慣性會將特殊符號加上 escape ( \ ),
這樣的寫法在 chrome 是沒問題的,如下:

<input type="text" class="form-control" required
   pattern="[\-\_]"
   >

但在 firefox 卻拋出錯誤訊息:
Unable to check <input pattern='[\-\_]'> because the pattern is not a valid regexp: invalid identity escape in regular expression


研究後發現,在 input pattern 不需要做 escape ,在 chrome, firefox 都 work,
修改後,如下:
<input type="text" class="form-control" required
   pattern="[-_]"
   >