html
前端
git
github
web
算法
數據庫
json
c#
api
什麼是KoobooJson?
- KoobooJson的優勢
- KoobooJson的實現 (後續我將出一篇新的文章詳細講解實現)
- 功能介紹
- 忽略註釋
- 忽略互引用所致使的堆棧循環
- 忽略Null值
- 排序特性
- Dictionary的Key格式
- JObject和JArray
- 忽略默認值元素
- 忽略序列化元素
- 序列化時僅包含該元素
- 時間格式
- 首字母大小寫
- 別名特性
- 反序列化時指定構造函數
- 值格式化特性
- 全局Key格式化
- 其它
KoobooJson的優勢
目前KoobooJson只有130k, 而且沒有任何額外依賴項, KoobooJson當前支持框架版本.NET4.5 .NET Core2+ .NET Standard 2
KoobooJson 遵循JSON RFC8259規範, 是一款適用於C#的快速的Json文本序列化器
它基於表達式樹構建, 在運行時會動態的爲每一個類型生成高效的解析代碼, 這過程包括: 利用靜態泛型模板進行緩存, 避免字典查詢開銷, 避免裝箱拆箱消耗, 緩衝池複用, 加速字節複製...
KoobooJson生成代碼的方式並無採用Emit, 而是採用ExpressionTree. ExpressionTree相比Emit而言, 它不能像Emit直接寫出最優的IL代碼, 它依賴於下層的編譯器, 在某些時候它會多生成一些沒必要要的IL代碼路徑, 故而性能上有所遜色. 但相較於幾乎沒有類型檢查的Emit而言, ExpressionTree不會出現各類莫名其妙的錯誤, 它更加安全, 也更加容易擴展維護.
雖然ExpressionTree與Emit相比在性能方面可能會有所差別, 可是KoobooJson的表現卻至關亮眼!
上圖是使用BenchmarkDotNet在Net Core2.1上作的Json序列化和反序列化的性能測試,隨機生成大量的測試數據,迭代100次後產生的結果,基準報告在這裏
BenchmarkDotNet=v0.11.4, OS=Windows 10.0.17763.316 (1809/October2018Update/Redstone5) Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=2.1.505 [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT Job-XEQPPS : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
IterationCount=100 LaunchCount=1 WarmupCount=1
在類型定義上, KoobooJson並無單獨實現每一個集合或鍵值對類型, 而是對這些FCL類型進行劃分紅不一樣的模板
a. KoobooJson將序列化分爲5種類型:
-
原始類型
它包括 Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single. -
全部擁有鍵值對行爲的類型
任何可以實現IDictionary<>或可以實現IDictionary且可以經過構造函數注入鍵值對的類型, 都將以鍵值對方式進行解析 -
全部擁有集合行爲的類型
任何可以實現IEnumable而且知足IColloction的Add行爲或擁有本身獨特的集合行爲且可以經過構造函數注入集合的類型, 都將以集合方式進行解析 -
特殊類型
如Nullable<>, Lazy<>, Guid, Datatable, DateTime, Type, Task, Thread, Timespan...等等這些特定的類型實現 -
常規Model的鍵值對類型
在KoobooJson中, 若是當類型不知足上述4種時, 將會以鍵值對的形式來對其解析, KoobooJson會對Model中公開的全部元素進行序列化, 在這個環節, 幾乎配置器中全部的配置都是有關Model的. 諸如別名, 忽略特性, 指定構造函數, 忽略堆棧循環引用, 首字母大小寫, 格式化器... 值得一提的是, 在對接口類型進行反序列化時, KoobooJson默認會自動建立並返回一個實現於該接口的對象.
b. 在對類型的解析上, 其中浮點型,日期時間類型, GUID的解析是參照了JIL的代碼, 在此表示感謝.
做爲一款活躍的Json庫, KoobooJson會不斷支持更多的類型, 這其中, 由於對FCL中的鍵值對和集合的行爲進行概括, 因此對於這兩種類型, KoobooJson並不像其它框架同樣去特定的爲每種類型單獨實現, 實際上, 第2和3所定義的規則能夠容納FCL中的大多數鍵值對或集合類型.
目前KoobooJson所覆蓋的類型包括 : Hashtable, SortedList, ArrayList, IDictionary, Dictionary<,>, IList,List<>, IEnumerable<>, IEnumerable, ICollection, ICollection<>, Stack<>, Queue<>, ConcurrentBag<>, ConcurrentQueue<>, ConcurrentStack<>, SortedDictionary<,>, ConcurrentDictionary<,>, SortedList<,>, IReadOnlyDictionary<,>, ReadOnlyDictionary<,>, ObservableCollection<>, HashSet<>, SortedSet<>, LinkedList<>, ReadOnlyCollection<>, ArraySegment<>, Stack, Queue, IReadOnlyList<>, IReadOnlyCollection<>, ReadOnlyCollection<>, ISet<>, BitArray, URI, NameValueCollection, StringDictionary, ExpandoObject, StringBuilder, Nullable<>, Lazy<>, Guid, Datatable, DateTime, Type, Task, Thread, Timespan, Enum, Exception, Array[], Array[,,,,,]...
KoobooJson的實現
class UserModel { public object Obj; public string Name; public int Age; } string json = JsonSerializer.ToJson(new UserModel());
在對類型第一次序列化時, KoobooJson會爲這個類型生成大體是這樣的解析代碼.
void WriteUserModel(UserModel model,JsonSerializerHandler handler) { ...配置選項處理...格式化器處理...堆棧無限引用處理... handler.sb.Write("Obj:") WriteObject(model.Obj);//在序列化時將爲Object類型作二次真實類型查找 handler.sb.Write("Name:") WriteString(model.Name); handler.sb.Write("Age:") WriteInt(model.Age); }
若是是List<UserModel>的話, 那麼將生成這樣的代碼
handler.sb.Write("[") foreach(var user in users) { WriteUserModel(user); WriteComma() } handler.sb.Write("]")
在當前版本中, KoobooJson序列化使用的容器爲StringBuilder, 與直接ref char[]相比, 多了一些額外的調用. 將考慮在下個版本中構建一個輕便的char容器, 並會區分對象大小, 考慮棧數組和經過預掃描大小來減小對內存的開銷,這將顯著提高序列化速度.
在對類型進行第一次反序列化時, KoobooJson會爲這個類型生成大體是這樣的解析代碼.
UserModel model = JsonSerializer.ToObject<UserModel>("{\"Obj\":3,\"Name\":\"Tom\",\"Age\":18}"); void ReadUserModel(string json,JsonDeserializeHandler handler) { ...Null處理... ReadObjLeft() 空元素處理...構造函數處理...配置項處理...格式化器處理... while(i-->0){ switch(gechar()) { case 'O': switch(getchar()) case 'b': switch(getchar()) case 'j': ReadQuote(); ReadObject(); if(getchar()==',') i++; } } ReadObjRight() }
KoobooJson生成反序列化代碼, KoobooJson會假設json格式徹底正確, 沒有預先讀取Json結構部分, 而是直接使用代碼來描述結構, 因此KoobooJson少了一次對json結構的掃描, 執行過程當中若是json結構發生錯誤, 會直接拋出異常.
而對於key的匹配, KoobooJson生成的是逐個char的自動機匹配代碼, 目前KoobooJson是以字典樹爲算法, 逐個char進行類型比較, 與一次比較多個char相比, 這種方式顯然沒有達到最小的查詢路徑, 不過在jit優化下, 兩種方式實現經測試效率幾乎同樣.
在反序列化讀取字符時, 由於是對類型動態生成編碼, 提早知道每一個類型中的元素的字節長度和其類型的值長度, 因此KoobooJson出於更高的性能對反序列化採起了指針操做, 並加速字節讀取.
case 3: if (*(int*)Pointer != *(int*)o) return false; if (*(Pointer + 2) != *(o + 2)) return false; goto True; case 4: if (*(long*)Pointer != *(long*)o) return false; goto True; case 5: if (*(long*)Pointer != *(long*)o) return false; if (*(Pointer + 4) != *(o + 4)) return false;
由於是指針操做, KoobooJson在反序列化環節幾乎不須要去維護一個char池來存放下一個須要讀取的json結構片斷.
功能介紹
KoobooJson當前支持6個API調用
string Kooboo.Json.JsonSerializer.ToJson<T>(T value, JsonSerializerOption option=null) T Kooboo.Json.JsonSerializer.ToObject<T>(string json, JsonDeserializeOption option=null) object Kooboo.Json.JsonSerializer.ToObject(string json, Type type, JsonDeserializeOption option=null)
void Kooboo.Json.JsonSerializer.ToJson<T>(T value, StreamWriter streamWriter, JsonSerializerOption option = null)
T Kooboo.Json.JsonSerializer.ToObject<T>(StreamReader streamReader, JsonDeserializeOption option = null)
object Kooboo.Json.JsonSerializer.ToObject(StreamReader streamReader, Type type, JsonDeserializeOption option = null)
在json字符串的讀取中KoobooJson會自動忽略註釋
string json = @" /*註釋*/ {//註釋 /*註釋*/""Name"" /*註釋*/: /*註釋*/""CMS"" /*註釋*/,//註釋 /*註釋*/ ""Children"":[//註釋 1/*註釋*/, 2/*註釋*/ ]//註釋 }//註釋 /*此處*/ "; var obj = JsonSerializer.ToObject(json); obj=>Name:CMS obj=>Children:Array(2)
class A { public B b; } class B { public A a; } A.b=B; B.a=A;
A指向B, B指向A, 在序列化時這種狀況會發生無限循環.可經過KoobooJson的序列化配置項中的屬性來設定這種狀況下所對應的結果
JsonSerializerOption option = new JsonSerializerOption { ReferenceLoopHandling = JsonReferenceHandlingEnum.Null }; string json = JsonSerializer.ToJson(a, option); json => {\"b\":{\"a\":null}} ------ ReferenceLoopHandling = JsonReferenceHandlingEnum.Empty json => {\"b\":{\"a\":{}}} ----- ReferenceLoopHandling = JsonReferenceHandlingEnum.Remove json => {\"b\":{}}
class A { public string a; } A.a=null; JsonSerializerOption option = new JsonSerializerOption { IsIgnoreValueNull = true }; var json = JsonSerializer.ToJson(A, option); json => {}
class A { [JsonOrder(3)] public int a; [JsonOrder(2)] public int b; [JsonOrder(1)] public int c; }
可經過[JsonOrder(int orderNum)]來排序序列化的json元素位置. 若是是正常沒有經過[JsonOrder]排序元素,那麼解析出來的Json則是默認順序:{"a":0,"b":0,"c":0} 上面樣例經過[JsonOrder]排序後是這樣的:{"c":0,"b":0,"a":0}
在Json規範中,鍵值對的鍵必須是字符串類型,在KoobooJson中,對Key的類型容許全部基元類型(Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single)和String,以及Datetime,GUID,Enum,共18種支持的類型
反序列化時,對Object的類型解析,最終將會產生5種結果: Bool,數值(long,ulong,double),String,JArray,JObject 其中,JArray表明着數組,它擁有List<object>的全部特性. JObject表明着鍵值對,它擁有Dictionary<string,object>的全部特性.
[IgnoreDefaultValue] class User { public int? Num { get; set; } public int Age { get; set; } public string Name { get; set; } } var json = JsonSerializer.ToJson(new User()); json=> {}
若是你想在序列化時,忽略默認值的元素,那麼能夠對這個類型標記[IgnoreDefaultValue]特性,也能夠標記在屬性或者字段上
class A { [IgnoreKey] public int a; public int b; }
可經過[IgnoreKey]特性來標記序列化和反序列化要忽略的元素 json => {"b":0} 固然, 也能夠經過配置來動態選擇忽略對象
JsonSerializerOption option = new JsonSerializerOption { IgnoreKeys = new List<string>(){"b"} }; var json = JsonSerializer.ToJson(A, option); json => {}
class A { [JsonOnlyInclude] public int a; public int b; public int c; } json => {\"a\":0}
若是一個model裏包含幾十個元素, 而你僅想序列化其中一個, 那麼就不必對每個元素進行[IgnoreKey]標記,只須要對想要序列化的元素標記[JsonOnlyInclude]便可
JsonSerializerOption option = new JsonSerializerOption { DatetimeFormat=DatetimeFormatEnum.ISO8601 }; json => 2012-01-02T03:04:05Z JsonSerializerOption option = new JsonSerializerOption { DatetimeFormat=DatetimeFormatEnum.RFC1123 }; json => Thu, 10 Apr 2008 13:30:00 GMT JsonSerializerOption option = new JsonSerializerOption { DatetimeFormat=DatetimeFormatEnum.Microsoft }; json => \/Date(628318530718)\/
class A { public int name; } JsonSerializerOption option = new JsonSerializerOption { JsonCharacterRead=JsonCharacterReadStateEnum.InitialUpper }; json => {\"Name\":0}
在對model序列化時能夠指定key的首字母大小寫,反序列化時也能夠設置對字符串不區分大小寫.首字母大小寫屬於內嵌支持, 在解析時並不會影響性能
class A { [Alias("R01_Name")] public int name; } json => {\"R01_Name\":0}
當元素被標記[Alias]後,KoobooJson不管序列化仍是反序列化都會按照Alias來進行解析
class A { public A(){} [JsonDeserializeCtor(3,"ss")] public A(int a,string b){} }
在反序列化的時候, 咱們不得不調用構造函數來以此建立對象. 在常規狀況下, KoobooJson會經過優先級自動搜索最合適的構造函數,其優先級順序爲: public noArgs => private noArgs => public Args => private Args, 這其中, 會對有參構造函數進行默認值構造.
然而你也能夠顯式經過[JsonDeserializeCtor(params object[] args)]特性來指定反序列化時的構造函數, 這樣 當KoobooJson建立A實例的時候就不是經過new A(); 而是new A(3,"ss");
class A { [Base64ValueFormat] public byte[] a; }
當你須要來覆寫由KoobooJson進行元素解析的行爲時, 咱們能夠繼承一個 ValueFormatAttribute 來覆寫行爲.
class Base64ValueFormatAttribute:ValueFormatAttribute { public override string WriteValueFormat(object value,Type type, JsonSerializerHandler handler, out bool isValueFormat) { isValueFormat=true; if(value==null) return "null"; else return ConvertToBase64((byte[])value); } public override object ReadValueFormat(string value,Type type, JsonDeserializeHandler handler, out bool isValueFormat) { isValueFormat=true; if(value=="null") return null; else return Base64Convert(value); } }
值格式化特性也能夠標記在結構體或類上, 而另外一點是對於值格式化器, 也能夠以全局的方式來進行配置: 以序列化爲例, 可經過 JsonSerializerOption中的GlobalValueFormat委託來進行配置
JsonSerializerOption.GlobalValueFormat=(value,type,handler,isValueFormat)=> { if(type==typeof(byte[])) { isValueFormat=true; if(value==null) return "null"; else return ConvertToBase64((byte[])value); } else { isValueFormat=false; return null; } }
值得注意的是,對於byte[]類型的base64解析行爲, KoobooJson已經內嵌在配置項中, 只要設置JsonSerializerOption.IsByteArrayFormatBase64=true便可
對於Model中的Key處理, KoobooJson支持全局的Key格式化器.
class R01_User { public string R01_Name; public int R01_Age; }
若是咱們想把R01這個前綴給去掉, 只須要註冊全局Key格式化器的委託便可
JsonSerializerOption.GlobalKeyFormat=(key,parentType,handler)=> { if(parentType==typeof(R01_User)) { return key.Substring(4); } return key; }
這樣,出來的json是這樣的:{"Name":"","Age":""
一樣, 對於反序列化,咱們也一樣應該註冊:
JsonDeserializeOption.GlobalKeyFormat=(key,parentType)=> { if(parentType==typeof(R01_User)) { return "R01_"+key; } return key; }