C#自定義Json序列化
鑑於網上的此類文章講的不那麼好,特在此重新講一下
建立一個.Net Core控制檯程式,本文程式碼需要Nuget包Newtonsoft。安裝後就可以開始了
首先交代一下使用的類
public abstract class EntityBase { public virtual long Id { get; set; } } public class Entity : EntityBase { public override long Id { get; set; } public string Name { get; set; } [MyJsonIgnore]作為模型的類public decimal Money { get; set; } public Category Category { get; set; } public List<Child> Children { get; set; } } public class Child : EntityBase { public string Name { get; set; } } public enum Category { Human = 0, Cat = 1, Dog = 2 }
預設情況下的序列化
public classMain程式碼Program { public static void Main() { var entity = new Entity { Id = 11, Name = "aa", Money = 1000, Category = Category.Human, Children = new List<Child> { new Child{ Id = 22, Name = "bb"} } }; var json = JsonConvert.SerializeObject(entity); } }
結果:
現在我們不想輸出Id,並且Name換成"名字"
方案一:使用Newtonsoft的原生特性,適用於所有此類序列化輸出都是相同的場景
主要特性
[JsonIgnore]:序列化成字串時,不帶上這個屬性
[JsonProperty]:序列化時,修改屬性名
如:作為模型的類修改如下
public abstract class EntityBase { public virtual long Id { get; set; } } public class Entity : EntityBase { [JsonIgnore] public override long Id { get; set; } [JsonProperty("名字")] public string Name { get; set; } public decimal Money { get; set; } public Category Category { get; set; } public List<Child> Children { get; set; } } public class Child : EntityBase { [JsonProperty("名字")] public string Name { get; set; } } public enum Category { Human = 0, Cat = 1, Dog = 2 }作為模型的類
然後Main程式碼不用改
結果
這種方案的好處是簡單,壞處就是隻有1種輸出
方案二:自己實現轉化器convertor,適用於任何場景
1、在序列化時,Newtonsoft提供了自定義的方案,只要寫一個類去繼承Newtonsoft.Json.JsonConvertor類即可。
public class MyConvertor : JsonConverter { public override bool CanConvert(Type objectType) { return true; //反序列化時先執行 } public override bool CanRead => false; //使用預設反序列化 public override bool CanWrite => true; public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); //反序列化程式碼 } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var jObject = new JObject(); var entity = value as Entity; var type = typeof(Entity); var props = type.GetProperties(); foreach (var prop in props) { var att = prop.GetCustomAttributes(typeof(MyJsonIgnoreAttribute), false).FirstOrDefault(); if (att != null) { continue; } var name = prop.Name; var att2 = prop.GetCustomAttributes(typeof(MyJsonPropertyAttribute), false).FirstOrDefault(); if (att2 != null) { name = ((MyJsonPropertyAttribute) att2).PropertyName; } var v = prop.GetValue(entity); jObject.Add(name, JToken.FromObject(v)); } jObject.WriteTo(writer); } } [AttributeUsage(AttributeTargets.Property)] public class MyJsonIgnoreAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property)] public class MyJsonPropertyAttribute : Attribute { public string PropertyName { get; } public MyJsonPropertyAttribute(string name) { PropertyName = name; } }Convertor類程式碼
注意1:本文只討論序列化,不需要反序列化的場景。所以CanRead=false,並且CanConvert跟ReadJson都沒有實現,如果想使用預設反序列化方案的,CanConvert返回true,CanConvert是表示使用此Convertor類反序列化時,模型物件能否反序列化,網上許多寫法都返回了bool--“當前類是否繼承某個類或實現某個介面來判斷能否反序列化”,但我認為,既然是預設反序列化方案的話,直接返回true即可,除非你也自定義反序列化方案,那就要判斷
public override bool CanConvert(Type objectType) { return objectType.IsAssignableFrom(typeof(EntityBase)); }
注意2:在程式碼中,我使用了自定義的特性來代替原生的JsonIgnoreAttribute和JsonPropertyAttribute,如果你確定只可能有1種輸出但又不想使用預設的序列化方案時,那麼可以不自定義特性,用回原生特性(效果都一樣)。使用自定義特性是為了當有其他的Convertor輸出方案時,可以不一樣。比如某個方案需要序列化json時,帶上Id欄位,某個方案又不帶上,還有個方案要求Name顯示成「名前」...em...一般極少2種及以上的情況的,所以極少需要自定義特性
注意3:在把生成的JObject物件寫到writer中時,網上的部落格大多數寫法是這樣的,各位看官可以驗證這種寫法最終序列化後會多出一次轉義操作,這是錯誤的。stackoverflow網站上的寫法也就是我這種jObject.WriteTo(writer);才是正確的
2、模型類使用特性,參考方案一那樣
補充1:可以在模型類上,使用特性來繫結序列化Convertor,表明我的預設序列化方案就是這個Convertor;當JsonConvert.SerializeObject呼叫時不傳convertor就是使用預設方案
[JsonConverter(typeof(MyConvertor))]
補充2:可以使用入參Formatting.Indented來使序列化的json換行
最後我選擇繼承泛型的JsonConvertor讓更多的模型可以使用這個Convertor,如這裡的Child類使用特性綁定了MyConvertor為預設序列化方案,全部程式碼整合如下:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; namespace Json { public class Program { public static void Main() { var entity = new Entity { Id = 11, Name = "aa", Money = 1000, Category = Category.Human, Children = new List<Child> { new Child{ Id = 22, Name = "bb"} } }; var convertor = new MyConvertor<Entity>(); var json = JsonConvert.SerializeObject(entity, Formatting.Indented, convertor); } } public abstract class EntityBase { public virtual long Id { get; set; } } public class Entity : EntityBase { [MyJsonIgnore] public override long Id { get; set; } [MyJsonProperty("なまえ")] public string Name { get; set; } public decimal Money { get; set; } public Category Category { get; set; } public List<Child> Children { get; set; } } [JsonConverter(typeof(MyConvertor<Child>))] public class Child : EntityBase { [MyJsonIgnore] public override long Id { get; set; } [JsonProperty("名字")] public string Name { get; set; } } public enum Category { Human = 0, Cat = 1, Dog = 2 } public class MyConvertor<TEntity> : JsonConverter<TEntity> { public override bool CanRead => false; //使用預設反序列化 public override bool CanWrite => true; public override TEntity ReadJson(JsonReader reader, Type objectType, TEntity existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); //反序列化程式碼 } public override void WriteJson(JsonWriter writer, TEntity entity, JsonSerializer serializer) { var jObject = new JObject(); var type = typeof(TEntity); var props = type.GetProperties(); foreach (var prop in props) { var att = prop.GetCustomAttributes(typeof(MyJsonIgnoreAttribute), false).FirstOrDefault(); if (att != null) { continue; } var name = prop.Name; var att2 = prop.GetCustomAttributes(typeof(MyJsonPropertyAttribute), false).FirstOrDefault(); if (att2 != null) { name = ((MyJsonPropertyAttribute)att2).PropertyName; } var v = prop.GetValue(entity); jObject.Add(name, JToken.FromObject(v)); } jObject.WriteTo(writer); } } [AttributeUsage(AttributeTargets.Property)] public class MyJsonIgnoreAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property)] public class MyJsonPropertyAttribute : Attribute { public string PropertyName { get; } public MyJsonPropertyAttribute(string name) { PropertyName = name; } } }全部程式碼
其實特性的邏輯程式碼應該在特性的類裡面寫的,這裡不搞這麼複雜了,因為筆者的文章面向於初學者
執行結果