.NET Core工程应用系列(2) 实现可配置Attribute的Json序列化方案
2021-12-15 11:18
741 查看
背景
在这篇文章中,我们实现了基于自定义Attribute的审计日志数据对象属性过滤,但是在实际项目的应用中遇到了一点麻烦。需要进行审计的对象属性中会包含其他类对象,而我们之前的实现是没办法处理这种类属性对象内部的Attribute的。另外,属性值为
null的会抛异常。
但是Newtonsoft自带的
JsonConverter.SerializeObject方法实际上是能够处理这些情况的,给类属性对象所属的类中某个属性添加的Attribute能够正常被处理。同时我们也希望这个Attribute仅仅在这种情况下被应用,项目中的其地方序列化忽略这个Attribute。
骚年,继续我们的填坑之旅。
解决方案
思路
首先既然原框架中的
JsonConverter.SerializeObject能够做到序列化类对象属性时处理另一个类中的诸如
JsonIgnore的Attribute,那这篇文章中我们重写
AuditDataProvider中的
Serialize方法时,就不能自定义序列化的操作,而是借用
JsonConverter.SerializeObject方法,然后想办法通过配置项来实现定制化的Attribute处理。
核心代码
打开Newtonsoft.Json的源代码进行查看,跟踪
JsonConverter.SerializeObject方法:
SerializeObject静态方法
public static string SerializeObject(object value, Type type, JsonSerializerSettings settings) { // 接收调用方法时传入的JsonSerializerSettings对象并构造JsonSerializer对象 JsonSerializer jsonSerializer = JsonSerializer.CreateDefault(settings); // 调用序列化对象操作 return SerializeObjectInternal(value, type, jsonSerializer); }
先看
CreateDefault方法:
public static JsonSerializer CreateDefault(JsonSerializerSettings settings) { JsonSerializer serializer = CreateDefault(); if (settings != null) { ApplySerializerSettings(serializer, settings); } return serializer; } private static void ApplySerializerSettings(JsonSerializer serializer, JsonSerializerSettings settings) { // ... 省略若干代码 if (settings.ContractResolver != null) { // 如果指定了ContractResolver,则使用我们指定的,否则使用默认的Resolver serializer.ContractResolver = settings.ContractResolver; } // ... 省略若干代码 }
SerializeObjectInternal方法
追踪方法
SerializeObjectInternal到深层,可以看到在内部调用的序列化逻辑是根据当前遇到的节点类型分别实现了不同的
WriteJson方法,以
KeyValueConverter的
WriteJson方法为例:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { ReflectionObject reflectionObject = ReflectionObjectPerType.Get(value.GetType()); // 使用ContractResolver对象进行后面的序列化,其实看到这里就可以了,我们大致可以推断出来具体解析一个对象 // 的工作,是由这个ContractResolver对象来定义的。 DefaultContractResolver resolver = serializer.ContractResolver as DefaultContractResolver; writer.WriteStartObject(); writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(KeyName) : KeyName); serializer.Serialize(writer, reflectionObject.GetValue(value, KeyName), reflectionObject.GetType(KeyName)); writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(ValueName) : ValueName); serializer.Serialize(writer, reflectionObject.GetValue(value, ValueName), reflectionObject.GetType(ValueName)); writer.WriteEndObject(); }
DefaultContractResolver
查看官方实现的一个
CamelCasePropertyNamesContractResolver类,继承自
DefaultContractResolver类。我们发现在基类中有这样一个虚方法:
protected virtual IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
这个方法说明了我们可以通过实现该方法来定义要获取当前对象中的哪些属性。解决方案已经很明显了,我们继承该基类,实现自己的
CreateProperties方法,在
CreateProperties方法中通过Attribute过滤需要序列化的属性集合返回即可。
代码实现
通过分析,我们推测使用自定义的
ContractResolver,在内部判断属性上的Attribute值,来返回过滤后的对象属性集合就能实现我们想要的功能。
添加自定义ContractResolver,重写CreateProperties方法
public class MyContractResolver<T> : DefaultContractResolver where T : Attribute { private readonly Type _attributeToIgnore; public MyContractResolver() { _attributeToIgnore = typeof(T); } protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { // 过滤出那些没有Ignore掉的属性集合 var list = type.GetProperties() .Where(x => x.GetCustomAttributes().All(a => a.GetType() != _attributeToIgnore)) .Select(p => new JsonProperty() { PropertyName = p.Name, PropertyType = p.PropertyType, Readable = true, Writable = true, ValueProvider = base.CreateMemberValueProvider(p) }).ToList(); return list; } }
使用自定义ContractResolver
修改
CustomFileDataProvider中的
Serialize方法:
public override object Serialize<T>(T value) { if (value == null) { return null; } // 传入自定义的MyContractResolver对象并指定需要忽略的Attribute类型 var js = JsonConvert.SerializeObject(value, new JsonSerializerSettings { ContractResolver = new MyContractResolver<UnAuditableAttribute>() }); return JToken.FromObject(js); }
测试结果
首先我们修改对象
Order,让它包含一个类对象属性:
public class OrderBase { [UnAuditable] public string Name { get; set; } } public class Order : OrderBase { public Guid Id { get; set; } [UnAuditable] public string CustomerName { get; set; } public int TotalAmount { get; set; } public DateTime OrderTime { get; set; } public Product Product { get; set; } public Order(string name, Guid id, string customerName, int totalAmount, DateTime orderTime, Product product) { Id = id; CustomerName = customerName; TotalAmount = totalAmount; OrderTime = orderTime; Product = product; Name = name; } public void UpdateOrderAmount(int newOrderAmount) { TotalAmount = newOrderAmount; } public void UpdateName(string name) { CustomerName = name; } } public class Product { [UnAuditable] public string ProductName { get; set; } public int ProductPrice { get; set; } public Guid ProductId { get; set; } }
修改
Main方法:
static void Main(string[] args) { ConfigureAudit(); var order = new Order("BaseName", Guid.NewGuid(), "Jone Doe", 100, DateTime.UtcNow, new Product { ProductId = Guid.NewGuid(), ProductName = "Some Product Name", ProductPrice = 30 }); using (var scope = AuditScope.Create("Order::Update", () => order)) { order.UpdateOrderAmount(200); order.UpdateName(null); // optional scope.Comment("this is a test for update order."); } }
运行程序,查看记录的审计日志:
$ cat Order::Update_637409019020799770.json { "EventType": "Order::Update", "Environment": { "UserName": "yu.li1", "MachineName": "Yus-MacBook-Pro", "DomainName": "Yus-MacBook-Pro", "CallingMethodName": "TryCustomAuditNet.Program.Main()", "AssemblyName": "TryCustomAuditNet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "Culture": "" }, "Target": { "Type": "Order", "Old": "{\"Id\":\"7f5f26af-fc09-45cf-9f2f-349d6a2a962c\",\"TotalAmount\":100,\"OrderTime\":\"2020-11-13T14:05:01.684625Z\",\"Product\":{\"ProductPrice\":30,\"ProductId\":\"7f3e2c16-fe20-43cc-ae18-594ddcb77ca9\"}}", "New": "{\"Id\":\"7f5f26af-fc09-45cf-9f2f-349d6a2a962c\",\"TotalAmount\":200,\"OrderTime\":\"2020-11-13T14:05:01.684625Z\",\"Product\":{\"ProductPrice\":30,\"ProductId\":\"7f3e2c16-fe20-43cc-ae18-594ddcb77ca9\"}}" }, "Comments": [ "this is a test for update order." ], "StartDate": "2020-11-13T14:05:01.694775Z", "EndDate": "2020-11-13T14:05:02.075199Z", "Duration": 380 }
那几个添加了
UnAuditableAttribute的属性已经不在我们的日志中了,收工,回家过周末。
总结
本文对应的代码在这里。
相关文章推荐
- 深入挖掘.NET序列化机制——实现更易用的序列化方案
- Redis下实现序列化保存和使用FastJson的处理(java)
- Lucene总结系列(三)--总述优化方案和呈现实时内存索引实现(结合RAMDirectory源码解析)
- Java通过fastJson实现redis序列化
- JSON 序列化和反序列化——JavaScriptSerializer实现
- RPC系列之--推荐系统实现方案(python)
- JSON 序列化和反序列化——JavaScriptSerializer实现
- jquery.getJSON跨域方案实现原理
- 【学艺不精系列】关于Json.NET的反序列化
- 一看就懂系列之 高并发的短链接替换实现方案
- .NET Framewok 3.5 中 JSON 序列化和反序列化的简单实现
- JSON 序列化和反序列化——JavaScriptSerializer实现
- C#中实现Json序列化与反序列化的几种方式
- C#实现简单的JSON序列化功能代码实例
- .NET Framewok 3.5 中 JSON 序列化和反序列化的简单实现
- 深入挖掘.NET序列化机制——实现更易用的序列化方案
- C#基础系列:实现自己的ORM(反射以及Attribute在ORM中的应用)
- 使用 ServiceStack.Text 序列化 json的实现代码
- [导入].Net3.5扩展方法实现对象JSON序列化
- pyramid 实现json数据传输时的对象序列化