Swifter.Json
Github
Wiki
在 .Net 平台上的一個功能強大,簡單易用,穩定又不失高性能的 JSON 序列化和反序列化工具。
Swifter.Json 已經經過了大量測試和線上項目中運行許久來確保它的穩定性。
特性
1: 支持 .Net 上絕大多是的數據類型,且輕松擴展;包括但不限於:實體,字典,集合,迭代器,數據讀取器和表格。
2: 支持 .Net 我已知的大多數平台,包括但不限於:.Net Framework 2.0+, .Net Core 2.0+, .Net Standard 2.0+, Mono, Xamarin, Unity(測試版本為 2018.3).
3: 它幾乎是無 BUG 的,如果您遇到了問題,可以在 Github 上發布一個 issue,或者 QQ:1287905882,我會盡力幫助您。
4:所有公開成員都有中文說明,中文語言人的福音😄;將來可能添加英文說明。
缺點
1: 暫沒有英文接口說明,但成員命名是英文的。
2:總共有三個 DLL 文件,Swifter.Core(278KB)(這是 Swifter 的核心庫,我不希望它與 Json 掛鈎,而是它作為一個巨人,為類庫開發者提供很多幫助),Swifter.Unsafe(10KB)(這是用 IL 生成的 DLL,用於指針操作;並不是不安全的),Swifter.Json(52KB)(Swifter 的 Json 解析部分);文件不大也不小。
在 .Net Standard 下還需要 System.Reflection.Emit 和 System.Reflection.Emit.Lightweight 庫。
3:在 Standard 和 Framework 3.5 及更低版本,Swifter.Json 性能可能略減;因為我不敢在這些版本上使用針對性的優化,因為這些版本缺少一些接口,並且可能會在一個未知的平台上運行(如 Unity 和 Xamarin)。
部分 .Net 現有的 JSON 工具特性對比
平台兼容性 ✓:兼容大多數平台的大多數版本;乄:兼容部分平台,且版本要求較高;✗:只能在單一平台上運行。
穩定性 ✓:在大多數測試中未出現 BUG;乄:一些不常見操作會出現 BUG;✗:常見操作會出現 BUG。
功能性 ✓:支持大多數的數據類型和方法;乄:支持常用的數據類型和方法;✗:部分常用數據類型和方法不支持。
擴展性 ✓:高度允許自定義格式和處理方式;乄:支持常用的格式設置;✗:不能自定義格式。
高性能 ✓:相比 Newtonsoft 平均快 4x 以上;乄:相比 Newtonsoft 平均快 2x 以上;✗:相比 Newtonsoft 差不多或者更慢。
小分配(內存) ✓:執行過程中分配的內存極少;乄:必要的內存占用較少;✗:執行過程中分配的大量的臨時內存。
大小(文件) ✓:小於 100KB;乄:大於 100KB 小於 500 KB;✗:大於 500 KB。
部分 .Net 現有的 JSON 工具性能對比
.Net Core 3.0 Previews running results.
.Net Framework 4.7.1 Previews running results.
圖中的數字代表用時(ms). 表格顏色隨用時從 綠色 漸變為 黃色。當用時超過 3 倍時將以亮黃色顯示。
Swifter.Json 第一次執行需要額外的時間來生成一個 “操作類(FastObjectRW<T>)” 后續會越來越快。所以如果您的程序需要長期運行,那么 Swifter.Json 是您優的選擇。如果您的程序不適用這種模式,那么 Swifter.Reflection 的 XObjectRW<T> 也許適合您,詳情請看 Wiki。
Swifter.Json 的工作原理
以下面的實體類作為例子,解釋 Swifter.Json 是如何序列化的。
public class Demo
{
public int Id { get; set; }
public string Name { get; set; }
}
當執行以下操作時,
JsonFormatter.SerializeObject(new Demo { Id = 1, Name = "Dogwei" });
Swifter.Json 首先會創建一個 JsonSerializer 實例(此實例是一個 internal class),此類實現了 Swifter.RW.IValueWriter 接口。
然后 Swifter.Json 會執行 Swifter.RW.ValueInterface<Demo>.WriteValue(jsonSerializer, demo); 操作。
在 ValueInterface<Demo>.WriteValue 里會匹配 Demo 的 IValueInterface<Demo> 的實現類;默認情況下,它會匹配到 Swifter.RW.FastObjectInterface<T> 這個實現類。
賦予泛型參數,然后執行 FastObjectInterface<Demo> 的 WriteValue(IValueWriter valueWriter, Demo value) 方法。
在該方法里,它首先檢查了 value 是否為 Null,如果是則執行 valueWriter.DirectWrite(null); 方法,表示在 JsonSerializer 寫入一個 Null,然后返回。
然后檢查 value 的引用,是否為 “父類引用,子類實例” 的對象,如果是則重新匹配子類的 IValueInterface<T> 實現類。
之后是:執行 var fastObjectRW = FastObjectRW<Demo>.Create();,創建一個數據讀寫器,它實現了 IDataReader<string> 和 IDataWriter<string> 接口。
然后初始化數據讀寫器:fastObjectRW.Initialize(value);,這相當於把數據讀寫器中的 Demo 上下文 (Context) 設置為 value。
再調用 valueWriter.WriterObject(IDataReader<string> dataReader); 方法。這就回到了 JsonSerializer 的 WriterObject 方法里。
在該方法里,首先直接寫入了一個 '{' 字符。
然后執行 dataReader.OnReadAll(this);,OnReadAll 是用 IL 生成一個方法,它會遍歷 Demo 中所有公開屬性的名稱和值寫入到 IDataWriter<string> 里。
這里補充:JsonSerializer 除了實現了 IValueWriter 接口外,還實現了,IDataWriter<string> 和 IDataWriter<int>,這兩個是寫入 “對象” 和 寫入 “數組” 的接口。
在 OnReadAll 里會執行兩個操作:dataWriter["Id"].WriteInt32(Context.Id); dataWriter["Name"].WriteString(Context.Name);。
dataWriter["Id"] 操作會寫入一個 '"Id":' 的 JSON 字符串;然后返回 IValueWriter 實例,因為 JsonSerializer 本身就是 IValueWriter 的實現類,所以返回它本身。
在 WriteInt32 里,JsonSerializer 器會執行 offset += Swifter.Tools.NumberHelper.Decimal.ToString(value, hGBuffer.GetCharPointer() + offset); Append(',');
補充:hGBuffer 是一個本地內存的緩存,是一個非托管內存,它必須要釋放,Swifter.Json 將釋放放在 try {} finally {} 里,以確保在任何情況下都會釋放。
offset 表示當前 Json 字符串的寫入位置。
NumberHelper 是一個高性能低分配的數字算法,主要包括 浮點數和整形的 ToString 算法 和 Parse 算法,它支持 2-64 進制;Decimal 表示十進制。
在 WriteString里 JsonSerializer 器會執行 Append('"'); InternalWriteString(value); Append('"'); Append(',');
在 InternalWriteString 里,JsonSerializer 會根據字符串的長度選擇兩種寫入方式;
第一種方式是擴容字符串兩倍的內存空間,然后將字符串全部寫入,以確保字符串在包含轉義字符時能夠完整寫入,此方式性能更好。
第二種方式是擴容字符串等量的內存空間,然后逐個字符寫入,當內存滿的時候再次擴容,直至字符串全部寫入。
當字符串長度大於 300 時選用第二種方式,否則選用第一種方式。
這兩種方式是參考了其他 JSON 開源庫之后最終采用我認為最好的方式,性能對比對應 ShortString 和 LongString 的 ser 測試。
完了之后會返回到 JsonSerializer 的 WriterObject 里,該方法會去掉最后一個 ',' 字符,然后拼上 '}' 字符,然后再拼上 ','。
然后返回到 JsonFormatter 的 SerializeObject 里,該方法會執行 new string(hGBuffer.GetCharPointer(), 0, jsonSerializer.offset - 1); 獲取該 JSON 字符串。然后釋放 JsonSerializer 器。最后再返回給調用者
釋放 JsonSerializer 時,它會一起將 hGBuffer 也釋放了。
至此,JSON 序列化工作就完成了。
以下解釋 Swifter.Json 的反序列化過程。還是那個 Demo 類。
// 現在我們得到一個 JSON 字符串。
var json = "{\"Id\":1,\"Name\":\"Dogwei\"}";
執行如下操作:
JsonFormatter.DeserializeObject<Demo>(json);
Swifter.Json 首先會 fixed json 取得 json 的內存地址 pJson;然會執行 var jsonDeserializer = new JsonDeserializer(pJson, 0, json.Length) 創建解析器實例,此類實現了 IValueReader 接口。
然后 Swifter.Json 會執行 ValueInterface<Demo>.ReadValue(jsonDeserializer); 操作。
在 ValueInterface<Demo>.ReadValue 里也是會匹配 Demo 的 IValueInterface<T> 的實現類;它還是會匹配到 FastObjectInterface<Demo> 這個類。
然后執行 FastObjectInterface<Demo> 的 ReadValue(IValueReader valueReader) 方法。
在該方法里,它進行沒有任何判斷,直接創建了一個 FastObjectRW<Demo>;因為這里是第二次創建,所以馬上就能創建好。
然后執行 valueReader.ReadObject(IDataWriter<string> dataWriter); 方法。現在回到 JsonDeserializer 的 ReadObject 方法里。
在該方法里,首先判斷 JsonValueType 是否等於 Object。如果不是則調用一個 NoObject 方法。
在 NoObject 方法里,如果 JsonValueType 是 String,Number 或 Boolean ,則拋出異常;如果是 Null 則直接返回,如果是 Array,則執行 dataWriter.As<int> 將對象寫入器轉為 數組寫入器,然后調用 ReadArray(IDataWriter<int> dataWriter); 方法。
如果 JsonValueType 是 Object 類型,則執行 dataWriter.Initialize() 操作,此方法內部會執行 Context = new Demo(); 操作。
然會跳過空白字符,找到一個鍵的開始索引,然后解析這個鍵得到字符串,如 "Id";如果格式不正確則會引發異常。
Swifter.Json 支持 單引號的鍵 和 雙引號的鍵 和 沒有引號的鍵。沒有引號的鍵會去除前后空白字符;比如 { Id : 123 } 得到 的鍵就是 "Id"。
得到鍵之后,解析器會跳過空白字符,然后判斷第一個字符是否等於 ':';如果不是,將會引發 JsonDeserializeException。
這次是 ':' 字符,將索引設為 ':' 處 +1,然后再跳過空白字符,來到 值 的位置,也就是 1 的位置。
此時將調用 dataWriter.OnWriteValue(string name, IValueReader valueReader); 方法來通知對象寫入器去讀取該值賦給 "Id" 屬性。
在 dataWriter.OnWriteValue 內部會根據 name 匹配在 Id 屬性,然后執行 Context.Id = valueReader.ReadInt32();
然會回到 JsonDeserializer 的 ReadInt32 方法;該方法會檢查當前索引處的 JsonValueType 是否為 Number,如果不是將引發異常或者進行類型轉換。
現在 JsonValueType 是 Number,ReadInt32 會首先執行 var numLength = NumberHelper.Decimal.TryParse(pJson + index, length - index, out int result); 操作。
該方法會嘗試解析一個常規十進制的整形字符串,並返回解析成功的字符數量,通過判斷該返回值是否等於 0 就能得否解析成功。
如果解析成功則 index += numLength; 然會返回。如果不成功則執行 Convert.ToInt32(DirectRead()) 解析;如果還是解析失敗則拋出異常,成功則繼續解析。
DirectRead 會解析 Json 的任何值,然后返回一個 object 值。該值可能是一個字符串,也可能是 double 或 int,也可能是字典或集合。這取決於這個 Json 值本身是什么類型。
數字解析完成之后返回到 dataWriter.OnWriteValue 方法,此方法賦值 Id 之后在返回到 dataWriter.ReadObject 方法里。
此時解析器會跳過空白字符,然后得到位於索引處的一個非空白字符;如果此字符為 '}' 則結束解析並返回,如果此字符是 ',' 則嘗試解析下一個 鍵。
注意這里是嘗試解析,也就是說如果,此時再次解析到 '}' 則也會正常結束解析;比如:{"Id":123,} 也會被正常解析,但 {"Id":123,,} 則會出現異常。
還有:當解析到 '}' 字符時,如果當前 '}' 對應 JSON 中第一個(根)對象的 '{',那么將結束解析!也就是說如果在該 '}' 之后還有內容的話會被忽略!
如果您想解析類似 {"Id":1}{"Id":2} 類似這樣的字符串,需要自定義 IValueInterface<T>。詳情請看 Wiki。
特別注意:如果您使用流(TextReader) 的方式進行解析,那么 Swifter.Json 將會讀取流的全部內容,但是只解析其第一個(根)對象,之后的內容會被忽略,並且您不能在流中讀取到任何內容!
此時找到 ',' 字符,然后跳過空白字符,然后又來到 鍵 的解析處,解析出 "Name" 還是一樣的找到 ':' 然后跳過空白字符來到 "Dogwei" 的索引處。
再次執行 dataWriter.OnWriteValue("Name", this); 操作來通知對象寫入器去讀取該值賦給 "Name" 屬性。
此時會來到 JsonDeserializer 的 ReadString 方法;該方法同樣會檢查當前索引處的 JsonValueType 是否為 String,如果不是將引發異常或者進行類型轉換。
然后解析器將讀取第一個字符('"')作為該字符串的引號,意味着字符串的開始和結束字符。
然后解析器會查找下一個該字符('"'),期間計數 '' 的數量;當出現 '' 字符時,判斷下一個字符是否為 'u' 如果是,則跳過包含 '' 在內的 6 個字符("\uAAAA"),如果不是則跳過兩個字符("\n")。
遍歷完成之后將判斷出否出現 '' 轉義符,如果沒有則直接返回這部分字符串的內容;如果有,則創建一個遍歷內容長度減去轉義內容的長度的空白字符串,該字符串長度剛好等於結果字符串,然后再次循環填充該字符串。
該方式在沒有 '' 轉義符時性能極佳,但是如果在有轉義符時性能較低,處於中游水平;但內存分配始終時最小的。