上一篇Newtonsoft.Json高級用法發布以後收到挺多回復的,本篇將分享幾點挺有用的知識點和最近項目中用到的一個新點進行說明,做為對上篇文章的補充。
閱讀目錄
"動態改變屬性序列化名稱"顧名思義:在不同場景下實體字段序列化後字段名稱不同,比如有下面實體A,正常序列化後json為{"Id":"123"}
public class A
{
public string Id { get; set; }
}
現在有兩種新場景A場景下 字段Id需要序列化為Key,B場景下字段Id需要序列化為id,那麼如何在不改變實體代碼情形下完成該功能呢?下面以樹形結構數據為例子進行講解。
各種各樣的前端樹形控件所要求數據格式不一樣,下面列舉幾種常見的樹形控件數據格式。
//bootstrap treeview,數據結構為
[
{
id:'1', //節點id
text: '父節點', //節點顯示文本
icon: 'glyphicon glyphicon-cloud-download', //節點圖標樣式
nodes:[{id:'2',text:'子節點'}] //子節點
}
]
//zTree
[
{ "id" : "1", "name" : "父節點1", "children" : [{id:'4',name:'子節點1'}] },
{ "id" : "2", "name" : "父節點2", "children" : [{id:'5',name:'子節點2'}] },
{ "id" : "3", "name" : "父節點3", "children" : [{id:'6',name:'子節點3'}] }
]
兩者之間字段對比
treeview zTree 節點id id id 顯示文本 text name 圖標 icon icon 子節點 nodes children標紅部分是數據格式區別,假設後台定義的樹形實體如下
/// <summary>
/// 樹形實體
/// </summary>
public class Tree
{
/// <summary>
/// 當前ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 文本
/// </summary>
public string Text { get; set; }
/// <summary>
/// 附加信息
/// </summary>
public string Tag { get; set; }
/// <summary>
/// 節點圖標
/// </summary>
public string Icon { get; set; }
/// <summary>
/// 子級
/// </summary>
public List<Tree> Childrens { get; set; }
}
現在的情形是這樣的,後台樹形實體已經定義完成,前台樹形控件使用的是treeview。有什麼辦法使後台序列化返回的json數據格式和控件所要求的保持一致呢。
方法一 修改實體Tree
/// <summary>
/// 樹形實體
/// </summary>
public class Tree
{
/// <summary>
/// 當前ID
/// </summary>
public string id { get; set; }
/// <summary>
/// 文本
/// </summary>
public string text { get; set; }
/// <summary>
/// 附加信息
/// </summary>
public string Tag { get; set; }
/// <summary>
/// 節點圖標
/// </summary>
public string Icon { get; set; }
/// <summary>
/// 子級
/// </summary>
public List<Tree> nodes { get; set; }
}
其中標紅部分是修改的,當然還需要修改對Tree實體賦值的代碼,這裡就不列出了。
方法二 前台js處理
var data=[
{"Id":"1","Text":"父節點1","Childrens":[
{"Id":"3","Text":"子節點1","Childrens":[{"Id":"5","Text":"子節點1-1"}]},
{"Id":"4","Text":"子節點2"}
]},
{"Id":"2","Text":"父節點2","Childrens":[
{"Id":"5","Text":"子節點3"}
]}]
//將後台返回數據轉換成treeview所需格式數據
handleChild(data);
console.log(data);
//轉換後台實體數據為treeview符合的數據格式
function handleChild(childs){
for(var i=0,length=childs.length;i<length;i++){
var item=childs[i];
item.id=item.Id;
item.text=item.Text;
item.nodes=item.Childrens;
//處理子節點
if(item.Childrens){
handleChild(item.Childrens);
}
delete item.Id;
delete item.Text;
delete item.Childrens;
}
}
以上兩種方法都可以很輕松的解決我上述提出的問題,項目進行到一半,treeview使用的也很好,一切都很太平。某一天遇到了一個難題,前台有個功能需要使用zTree實現。但是需要保證之前使用treeView的功能模塊不變,又得支持zTree數據格式,先來分析一下上面兩種方案看還能不能繼續使用,方案一,可以新建一個樹形實體專門和zTree對應。方案二,重新實現一套數據轉換代碼。以上兩種方案缺點很明顯,前後端依賴太強,前台換了控件導致變動過大。
在思考有沒有更好的解決方案時,我想到了高級序列化用法中自定義序列化的字段名稱這一條,既然Newtonsoft.Json提供了實體字段A序列化成B的特性,那麼現在唯一需要解決的問題:怎麼動態修改這個映射關系。經過一番嘗試和閱讀源代碼,終於找到了下面最佳實踐。
/// <summary>
/// 動態屬性轉換約定
/// </summary>
/// <remarks>
/// 處理場景 樹形結構數據 後台代碼實體定義 為 Id Childrens 但是前台樹形控件所需數據結構為 id,nodes
/// 這個時候可以使用該屬性約定轉換類 動態設置 序列化後字段名稱
/// </remarks>
/// <example>
/// JsonSerializerSettings setting = new JsonSerializerSettings();
/// setting.ContractResolver = new PropsContractResolver(new Dictionary<string, string> { { "Id", "id" }, { "Text", "text" }, { "Childrens", "nodes" } });
/// string AA = JsonConvert.SerializeObject(cc, Formatting.Indented, setting);
/// </example>
public class PropsContractResolver : DefaultContractResolver
{
Dictionary<string, string> dict_props = null;
/// <summary>
/// 構造函數
/// </summary>
/// <param name="props">傳入的屬性數組</param>
public PropsContractResolver(Dictionary<string, string> dictPropertyName)
{
//指定字段要序列化成什麼名稱
this.dict_props = dictPropertyName;
}
protected override string ResolvePropertyName(string propertyName)
{
string newPropertyName = string.Empty;
if (dict_props != null && dict_props.TryGetValue(propertyName, out newPropertyName))
{
return newPropertyName;
}
else
{
return base.ResolvePropertyName(propertyName);
}
}
}
調用代碼實例
string type="zTree";
//字段映射關系
Dictionary<string, string> _dictProp = null;
if(type=="zTree"){
_dictProp = new Dictionary<string, string> { { "Icon", "icon" }, { "Text", "name" }, { "Childrens", "children" } };
}else if(type=="treeview"){
_dictProp = new Dictionary<string, string> { { "Icon", "icon" }, { "Text", "text" }, { "Childrens", "nodes" } };
}
// 序列化設置
JsonSerializerSettings PropSettings = new JsonSerializerSettings {
ContractResolver = new PropsContractResolver(_dictProp)
};
return JsonConvert.SerializeObject(new List<Tree>(), Formatting.None, PropSettings);
使用了動態改變屬性序列化名稱方案後,前後台完全解綁了,不管前台使用什麼樹形控件,後台實體只有一個樹形實體。我們要做的僅僅是設置一下字段映射關系而已。
回到頂部
默認情況下對於實體裡面的枚舉類型系統是格式化成改枚舉對應的整型數值,那如果需要格式化成枚舉對應的字符怎麼處理呢?Newtonsoft.Json也幫我們想到了這點,下面看實例
public enum NotifyType
{
/// <summary>
/// Emil發送
/// </summary>
Mail=0,
/// <summary>
/// 短信發送
/// </summary>
SMS=1
}
public class TestEnmu
{
/// <summary>
/// 消息發送類型
/// </summary>
public NotifyType Type { get; set; }
}
JsonConvert.SerializeObject(new TestEnmu());
輸出結果:
現在改造一下,輸出"Type":"Mail"
public class TestEnmu
{
/// <summary>
/// 消息發送類型
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public NotifyType Type { get; set; }
}
其它的都不變,在Type屬性上加上了JsonConverter(typeof(StringEnumConverter))表示將枚舉值轉換成對應的字符串,而StringEnumConverter是Newtonsoft.Json內置的轉換類型,最終輸出結果

全局參數設置功能是我最喜歡使用的功能,現在做的mvc項目,我都會先設定空值處理,減少不必要的流量損耗。上篇文章開篇說了,最初研究Newtonsoft.Json是從移動端項目開始的,無用字段空值字段不返回。
Newtonsoft.Json.JsonSerializerSettings setting = new Newtonsoft.Json.JsonSerializerSettings();
JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
{
//日期類型默認格式化處理
setting.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";
//空值處理
setting.NullValueHandling = NullValueHandling.Ignore;return setting;
});
回到頂部
另外有關自定義類型轉換問題可以參考Newtonsoft.Json高級用法第九條。序列化庫深入使用之後,由衷的佩服作者,可以將一個序列化庫做的如此強大,在學習它源代碼的同時對自己代碼設計理念也產生了很大的影響。感謝Newtonsoft.Json,後續有好的問題會在本篇文章進行續寫。
如果,您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】按鈕。
如果,您希望更容易地發現我的新博客,不妨點擊一下綠色通道的【關注我】。
因為,我的寫作熱情也離不開您的肯定支持。
感謝您的閱讀,如果您對我的博客所講述的內容有興趣,請繼續關注我的後續博客,我是焰尾迭 。