分享處理JSON反序列化轉回物件的建構式相關問題。
就拿早先文章提到的Ticker類別當例子:
publicclass Ticker
{
readonlystring symbol;
readonlystring market;
publicstring Symbol { get { return symbol; } }
publicstring Market { get { return market; } }
publicstring FullSymbol
{
get { return Symbol + "." + Market; }
}
public Ticker(string symbol, string market)
{
this.symbol = symbol;
this.market = market;
}
public Ticker(string fullsymbol)
{
var p = fullsymbol.Split('.');
if (p.Length != 2) thrownew ArgumentException();
this.symbol = p[0];
this.market = p[1];
}
}
我們建立一個2330.TW Ticker,以Json.NET序列化成JSON字串,再反序列化回Ticker物件:
staticvoid Main(string[] args)
{
var t1 = new Ticker("2330", "TW");
var json = JsonConvert.SerializeObject(t1);
Console.WriteLine("JSON={0}", json);
try {
var t2 = JsonConvert.DeserializeObject<Ticker>(json);
Console.WriteLine("Restored={0}", t2.FullSymbol);
}
catch (Exception ex)
{
Console.WriteLine("Error: {0}", ex.Message);
}
Console.Read();
}
觸礁了~
JSON={"Symbol":"2330","Market":"TW","FullSymbol":"2330.TW"}
Error: Unable to find a constructor to use for type OpTest.Ticker.
A class should either have a default constructor, one constructor with arguments or a
constructor marked with the JsonConstructor attribute. Path 'Symbol', line 1, position 10.
由於Ticker有兩個建構式,Json.NET在反序化時無法決定該用哪一個。
當物件有預設建構式(不傳任何參數),Json.NET會先以預設建構式建立物件,再一一設定屬性值。若遇到屬性值只能由物件內部指定(例如:public string Prop { get; private set; })或像Ticker使用readonly欄位,屬性只能透過建構式設定的狀況,類別不一定有預設建構式可用。
Json.NET很聰明,即使是帶參數的建構式,也會試著將JSON裡的屬性做為參數傳。例如:當JSON為{"Symbol":"2330","Market":"TW","FullSymbol":"2330.TW"}而建構式為public Ticker(string symbol, string market),Json.NET會偵測參數名稱symbol、market,在JSON屬性中尋找相同名稱的屬性值(忽略大小寫),找不到則填入參數型別預設值,一樣可以建構物件。
在Ticker案例問題則出在有兩個建構式,Json.NET無法判斷要用哪一個:
public Ticker(string symbol, string market)
public Ticker(string fullSymbol)
所幸,威力強大如Json.NET,當然已料想到這類情境,提供了JsonConstructorAttribute,我們只需在其中一個建構式加上[JsonConstructor]即可解決問題:
[JsonConstructor]
public Ticker(string symbol, string market)
{
this.symbol = symbol;
this.market = market;
}
最後,來對程式進行優化,順便展現Json.NET的彈性。
大家有沒有發現Ticker物件轉出的JSON字串包含Symbol、Market、FullSymbol,資料重複性太高?
{"Symbol":"2330","Market":"TW","FullSymbol":"2330.TW"}
嚴格來說,只要留FullSymbol就好,用JsonIgnoreAttribute排除Symbol與Market。另外,FullSymbol有點囉嗦,
我們用JsonProperty("Tick")幫它改個簡短的名字:
publicclass Ticker
{
readonlystring symbol;
readonlystring market;
[JsonIgnore]
publicstring Symbol { get { return symbol; } }
[JsonIgnore]
publicstring Market { get { return market; } }
public Ticker(string symbol, string market)
{
this.symbol = symbol;
this.market = market;
}
[JsonConstructor]
public Ticker(string fullsymbol)
{
var p = fullsymbol.Split('.');
if (p.Length != 2) thrownew ArgumentException();
this.symbol = p[0];
this.market = p[1];
}
[JsonProperty("Tick")]
publicstring FullSymbol
{
get
{
return Symbol + "." + Market;
}
}
}
修改後的JSON樣式及測試結果如下:
JSON={"Tick":"2330.TW"}
Restored=2330.TW
JSON的長度由54個字元
{"Symbol":"2330","Market":"TW","FullSymbol":"2330.TW"}
縮短成18個字元
{"Tick":"2330.TW"}
體積縮小到1/3,這招在資料筆數量多或對傳輸量斤斤計較的場合很管用,提供大家參考。
感想:Json.NET並不是檯面上效能最好的JSON程式庫,但功能完整性及成熟度實在讓人無話可說,還是老話一句,Json.NET真該納入.NET核心的。
好了,又到了呼口號時間: