CODE-自動產生對應Javascript物件的.NET類別
0 |
Abstract: This is a code generator to declare reflected .NET class of Javascript object using JSON.NET JObject features.
這是跟同事在討論系統架構時冒出的議題...
網頁前端將使用者輸入結果組裝成結構單純的Javascript物件,一個欄位對應一個屬性,但有些欄位如電話、地址等可能有多筆,故屬性型別除了字串、數字外,也有會有電話號碼物件陣列,電話號碼物件則包含國碼、區碼、號碼三個屬性。組裝完成的Javascript透過JSON.stringify會以字串形式傳至後端,針對這種前端動態組成的JSON,我們可在後端沒有事先宣告對應.NET Class的狀況下,將其反序列化成JSON.NET的JObject物件,再透過JObject.Properties探索其中的細節,這就是昨天文章試圖展示的重點。
不過,如果我還是想在寫.NET Code時享受美妙Intellisense,讓我輸入屬性名稱可以少打幾個字,並明確知道屬性類別;而當我豬頭打錯屬性名稱時,也很渴望Visual Studio直接在錯字下方餵我吃一條紅蚯蚓...
嗯,要怎麼收獲先怎麼裁,我們唯一的選擇就是乖乖在Server-Side把對應Javascript物件的.NET類別宣告出來,才能享有這一切。那就看著Javascript物件規格,認命地靠手工把一個個屬性打上去吧... 等等! 若只能乖乖打字修行,又何來此文?
是的,我又想偷懶了!!
想要享受Intellisense又不甘願打字,所以我試寫了一個自動依Javascript物件規格建立對應.NET Class的程式碼產生器。以下是個簡單示範,網頁上有兩個TextArea,上方放JSON字串,下方則是分析JSON字串後產生的.NET類別宣告程式碼。
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>JSON 2 Class</title>
<script type="text/javascript" src="jquery-1.4.2.js"></script>
<script type="text/javascript">
$(function () {
if ($("#txtJson").val() == "") {
var obj = {
Name: "Jeffrey",
RegTime: new Date(),
Level: 99,
Records: [1024, 9999, 32767],
Rank: { Code: "DKNT", Name: "DarkNight" },
Skills: [
{ Name: "Sword", Level: 1 },
{ Name: "Steal", Level: 2 },
{ Name: "MouthCannon", Level: 99 }
]
};
var json = JSON.stringify(obj);
$("#txtJson").val(json);
}
});
</script>
</head>
<body>
<form id="form1" runat="server">
<textarea id="txtJson" runat="server" rows="6" cols="60"></textarea>
<br />
<asp:Button ID="btnGo" runat="server" Text="Convert" onclick="btnGo_Click" />
<br />
<textarea id="txtClass" runat="server" rows="18" cols="60"></textarea>
</form>
</body>
</html>
執行結果如下。在這個範例中,Javascript的Rank屬性是具有Code, Name屬性的物件,而Skills則是個物件陣列,陣列元素物件具有Name, Level兩個屬性。因此,在產生的.NET Class宣告中,除了主類別CRoot用來承接JSON轉換結果外,還要多宣告CRank跟CSkills兩個子類別。
好,看看程式怎麼寫:
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
using System.Text;
public partial class JSON2Class_Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnGo_Click(object sender, EventArgs e)
{
JObject jo =
JsonConvert.DeserializeObject<JObject>(txtJson.Value);
JsonClassParser.RegisterClass(jo, "Root");
txtClass.Value = JsonClassParser.GenClassCode();
}
}
public class JsonClassParser
{
public class RawClass
{
public string ClassName;
public string PropertyHash;
public Dictionary<string, string> Properties;
}
//蒐集所有類別
public static List<RawClass> Library
= new List<RawClass>();
private static string getCLSType(JTokenType t)
{
switch (t)
{
case JTokenType.Boolean:
return "bool";
case JTokenType.Bytes:
return "byte[]";
case JTokenType.Date:
return "DateTime";
case JTokenType.Float:
return "decimal";
case JTokenType.Integer:
return "int";
case JTokenType.String:
return "string";
default:
throw new ApplicationException(
t.ToString() + " is not supported");
}
}
//註冊類別
public static string RegisterClass(JObject jo, string className)
{
RawClass c = new RawClass();
c.ClassName = "C" +
className ??
Path.GetFileNameWithoutExtension(
Path.GetTempFileName());
//將所有類別組成Hash字串,用以比對類別是否相同
c.PropertyHash =
string.Join(",",
jo.Properties().Select(o => o.Name).ToArray());
c.Properties = new Dictionary<string, string>();
foreach (JProperty p in jo.Properties())
{
string t = "";
switch (p.Value.Type)
{
case JTokenType.Object:
t = RegisterClass((JObject)jo[p.Name], p.Name);
break;
case JTokenType.Array:
JArray ary = (JArray)jo[p.Name];
string typeName = null;
foreach (JToken jv in ary)
{
if (jv.Type == JTokenType.Array)
throw new ApplicationException(
"Array of array is not supported!");
string s =
jv.Type == JTokenType.Object ?
RegisterClass((JObject)jv, p.Name) :
getCLSType(jv.Type);
if (typeName == null)
typeName = s;
else if (typeName != s)
throw new ApplicationException(
"Complex array is not supported!");
}
t = typeName + "[]";
break;
case JTokenType.Boolean:
case JTokenType.Date:
case JTokenType.Float:
case JTokenType.Integer:
case JTokenType.String:
t = getCLSType(p.Value.Type);
break;
default:
throw new ApplicationException(
p.Type.ToString() + " is not supported!");
}
c.Properties.Add(p.Name, t);
}
//檢查是否已有重覆類別存在
var q = (from o in Library
where o.PropertyHash == c.PropertyHash
select o).SingleOrDefault();
//不存在時新增
if (q == null)
{
Library.Add(c);
return c.ClassName;
}
else //存在時傳回既有類別
return q.ClassName;
}
public static string GenClassCode()
{
StringBuilder sb = new StringBuilder();
foreach (RawClass rc in Library)
{
sb.AppendFormat("public class {0} {{\r\n", rc.ClassName);
foreach (string p in rc.Properties.Keys)
{
sb.AppendFormat(" public {0} {1} {{ get; set; }}\r\n",
rc.Properties[p], p);
}
sb.AppendLine("}");
}
return sb.ToString();
}
}
雖然演算邏輯有點小複雜,但還是能在150列內將程式寫完,C#真是個好語言呀!!
程式先藉用JSON.NET的JObject動態物件模型逐一找出屬性,分析其型別,當型別為物件時,則用遞迴技巧再針對物件分析生出對應的子類別。當型別為陣列時,則分析陣列元素型別,決定宣告成何種陣列? 不過為避免過度複雜,我設了限制--陣列所有元素必須為同一型別! 當然,陣列元素型別也可以是物件,一樣用遞迴方式產生對應.NET子類別,但一樣受陣列元素單一型別的限制。另外,同型別物件可能被分析多次,我透過比對所有屬性名稱判斷類別是否為同一個。
程式未經大量測試,但初步試玩OK。有興趣的人可以拿回去玩玩,發現有Bug請再告知。
Comments
Be the first to post a comment