上一篇 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实体赋值的代码,这里就不列出了。
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,后续有好的问题会在本篇文章进行续写。
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的 【 推荐 】 按钮。
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的 【 关注我 】 。
因为,我的写作热情也离不开您的肯定支持。
感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是焰尾迭 。