Newtonsoft.Json动态过滤属性
Newtonsoft.Json动态过滤属性
接口写的多了,会发现很多的问题。同一个dto,不同的action返回的字段个数不一样。往往开发人员因为懒或者各种原因一股脑的全返回,会浪费很多流量且用户体验很差。
当然也会有负责一些的,根据不同的action定义不同的output类。毫无疑问这很麻烦,浪费开发时间。博主本人也是深受其扰,之前看到一篇博文 Newtonsoft.Json高级用法 里面有说到动态决定是属性是否序列化。深得我心于是上手试了一下。博文里的代码很不完善
本人的项目返回类型均为 OutputModel(Status、Message、Data(数据))。把output丢给json序列化的时候,拿到的只有属性只有Status、Message、Data。而我们要针对的是Data类型的属性。本人进行改造后。由于过滤的属性清单与OutPutModel的又不一致(因为我们要序列化的是output,我们传入的是data里的属性),无法进行序列化,后面又实验了一下效率如何。如下图,5w次循环竟然与之前差了71倍之多。于是博主准备自己动手去写一个
自己动手
动手之前应该思考一下,我们需要的是什么效果。
- 根据传入数组动态决定哪些属性需要初始化
- 针对OutPutModel的处理
- 效率的高效,至少要直接序列化的差距不会太大
我想要用法是在序列化的时候传入一个字符串数组(也就是需要序列化属性),这里对思路进行梳理一下
return Json(output,new []{"Name","Age"})
- 使用反射,拿到output.Data的Type
- 使用type创建一个实例,循环type的GetProperties。判断property是否在传入的字符串数组中
- 使用property.GetValue获取到属性值,然后再SetValue到创建的实例中
- 把实例赋值给output.Data
很简单,就是对output的data进行一次替换,下面代码很完美的可以解决问题。
private static void Filtered(OutputModel output, string[] param) { Type type = null; if (param == null || param.Length <= 0) { return; } type = null; if (output.Data == null) return; type = output.Data.GetType(); object result = Activator.CreateInstance(type); foreach (var property in type.GetProperties()) { if (!((IList)param).Contains(property.Name)) continue; object value = property.GetValue(output.Data, null); property.SetValue(result, value); } output.Data = result; }
细心的同学可能会发现上面的少了针对List的处理
Type type = null; if (output.Data is IList) { var dataList = output.Data as IList; if (dataList.Count > 0) { type = dataList[0].GetType(); var result = Activator.CreateInstance(output.Data.GetType()) as IList; foreach (var temp in dataList) { var instance = Activator.CreateInstance(type); foreach (var property in type.GetProperties()) { if (!((IList) param).Contains(property.Name)) continue; object value = property.GetValue(temp, null); property.SetValue(instance, value); } result.Add(instance); } output.Data = result; } }
上面的一些代码运行起来效率比原生5000次只低不50-100ms,完全可以接受。但是会发现序列化后我们不想要被序列化的属性值变成了null。研究了一下问题出在
var instance = Activator.CreateInstance(type); 这里,因为我们创建的还是那个类嘛。。属性也无法被砍掉。想了想这里可以用动态类型去实现。
type = obj.GetType(); var result = new ExpandoObject() as IDictionary<string, Object>; foreach (var property in type.GetProperties()) { if (retain) { if (!((IList)props).Contains(property.Name.ToLower())) continue; } else { if (((IList)props).Contains(property.Name.ToLower())) continue; } object value = property.GetValue(obj, null); result.Add(property.Name, value); } obj = result;
实验了一下对效果非常满意,另外一个有意思的事情是效率竟然比原生要快上一倍
这张图直接进行序列化
这张图是用过滤了属性
下面除上完整的代码,有需要的同学可以进行使用
using System; using System.Collections; using System.Collections.Generic; using System.Dynamic; using Tool.Response; namespace MvcCustommade { public class LimitPropsContractResolver { private static object Filtered(object obj, string[] props, bool retain) { if (obj == null) return null; Type type = null; if (obj is IList) { var dataList = obj as IList; if (dataList.Count > 0) { type = dataList[0].GetType(); List<IDictionary<string, object>> result = new List<IDictionary<string, object>>(); foreach (var temp in dataList) { var instance = new ExpandoObject() as IDictionary<string, Object>; foreach (var property in type.GetProperties()) { if (retain) { if (!((IList)props).Contains(property.Name.ToLower())) continue; } else { if (((IList)props).Contains(property.Name)) continue; } object value = property.GetValue(temp, null); instance.Add(property.Name, value); } result.Add(instance); } obj = result; } } else { type = obj.GetType(); var result = new ExpandoObject() as IDictionary<string, Object>; foreach (var property in type.GetProperties()) { if (retain) { if (!((IList)props).Contains(property.Name.ToLower())) continue; } else { if (((IList)props).Contains(property.Name.ToLower())) continue; } object value = property.GetValue(obj, null); result.Add(property.Name, value); } obj = result; } return obj; } /// <summary> /// 过滤无用的属性 /// </summary> /// <param name="output">返回的数据</param> /// <param name="props">过滤的属性数组</param> /// <param name="retain">过滤的数组是包含还是不包含</param> public static object CreateProperties(object output, string[] props, bool retain) { if (props == null || props.Length <= 0) { return output; } if (output == null) { return null; } for (int i = 0; i < props.Length; i++) { props[i] = props[i].ToLower(); } if (output is OutputModel) { var outputModel = output as OutputModel; if (outputModel.Data == null) { return outputModel; } outputModel.Data = Filtered(outputModel.Data, props, retain); return outputModel; #region 初始版 //Type type = null; //if (outputModle.Data is IList) //{ // var ss = outputModle.Data as IList; // if (ss.Count > 0) // { // type = ss[0].GetType(); // List<IDictionary<string, object>> result = new List<IDictionary<string, object>>(); // foreach (var temp in ss) // { // var instance = new ExpandoObject() as IDictionary<string, Object>; // foreach (var property in type.GetProperties()) // { // if (retain) // { // if (!((IList)props).Contains(property.Name)) continue; // } // else // { // if (((IList)props).Contains(property.Name)) continue; // } // object value = property.GetValue(temp, null); // instance.Add(property.Name, value); // } // result.Add(instance); // } // outputModle.Data = result; // } //} //else //{ // if (outputModle.Data == null) return; // type = outputModle.Data.GetType(); // var result = new ExpandoObject() as IDictionary<string, Object>; // foreach (var property in type.GetProperties()) // { // if (retain) // { // if (!((IList)props).Contains(property.Name)) continue; // } // else // { // if (((IList)props).Contains(property.Name)) continue; // } // object value = property.GetValue(outputModle.Data, null); // result.Add(property.Name, value); // } // outputModle.Data = result; //} #endregion } else { output = Filtered(output, props, retain); return output; } } } }