手寫Json解析器學習心得

噢~從"{"開始,看來是個對象了!

一. 介紹

一週前,老同窗阿立給我轉了一篇知乎回答,答主說檢驗一門語言是否掌握的標準是實現一個Json解析器,網易遊戲過去的Python入門培訓做業之一就是五天時間實現一個Json解析器。git

知乎回答---連接github

該回答對應的問題說起了一個開源的「從零開始的JSON庫教程」,剛好我剛開始學習go語言,對Json的理解也僅停留在一種端到端之間交互的數據格式,因而便跟着教程寫了一遍,受益良多,至少對我這種編程經驗少的人來講十分有幫助,如下是個人學習心得。golang

從零開始的JSON庫教程地址---連接編程

本身的實現---連接json

二. 整體收穫

1. 測試與重構

其實在剛開始接觸編程的時候,也常常據說要給本身的代碼寫測試,可是一直沒有學過相關的方法論,也不知道如何實踐,直到在公司實習的時候才慢慢意識到測試的重要性。當時每寫完一個功能,導師都會要求我造數據進行測試,從我當時的理解上看,本身寫測試用例的目的在於儘可能去覆蓋用戶的各類行爲,保證系統運行的穩定性。數組

可是在經歷了這門Json解析器教程後,我對編寫測試用例又有了更進一步的理解。該教程詳細地介紹了一種叫TDD的開發模式,中文是測試驅動開發,並從第一單元開始就貫徹執行。安全

在我看來,先寫測試後進行開發能幫助咱們明確咱們想要開發的功能,減小咱們走彎路的可能性。但有時提早作計劃每每不太容易,可能會出現測試不太好寫的狀況,這個時候咱們先把功能開發出來反而會更輕鬆一些。教程做者也推薦咱們在實際開發中兩種風格並用,以達到平衡。數據結構

說實話,剛開始看到本身的代碼能順利經過所有測試的時候還蠻有成就感的。可是隨着課程的深刻,我發現完備的測試不僅是給我成就感這麼簡單,更多的是一種安全感。框架

由於隨着解析器的功能增長,咱們的代碼會出現一些通用的模塊,爲了提升通用性,咱們須要進行重構,而完備的單元測試是咱們放膽去重構的重要保障。函數

另外,因爲和教程使用的語言不同,有些地方須要按本身的理解去寫,不可以全盤照搬,許多地方一開始實現得不太周全。我印象最深的地方是一開始咱們要解析null,false,true,數字和字符串,這些都是單個功能,各自經過單獨的測試用例不會很難。可是當咱們要解析數組的時候,因爲數組中有多個值,並且還可能有嵌套數組,這個時候就要保證單個值的解析不影響全局的解析。

我當時在作數組解析的時候遇到了很多的問題,基本都是單值解析的代碼不夠完善而致使的。還好以前跟着教程寫了足夠的測試用例,支撐着我把整個數組解析功能寫正確,今後愛上寫單元測試。

2. C語言的魅力

教程是用標準的C語言寫的,做者自己是C/C++的大牛,功力深厚。雖然我對C語言瞭解很少,可是跟着教程的解釋去閱讀C代碼也沒有太大的問題。

教程關於C語言的知識點不少,好比宏的定義,內存的分配與釋放,內存泄漏檢測等,最令我讚歎的是做者對指針的運用,太精巧了。雖然Go語言裏面也有指針,可是在我作這個教程的過程當中,Go的指針更多時候只是用來傳址。也因爲指針沒那麼強大,我不太方便像做者同樣實現一個通用強大的堆棧,用於暫存Json的解析內容。但Go語言有強大的Slices,用起來也很爽,很方便。

既然是用不一樣的語言實現一樣的功能,那咱們確定要充分發揮本身所用語言的優點了,這也是咱們想經過項目入門一門語言的關鍵。

三. 項目各個階段的收穫

1. 啓程

開始的第一章中我最大的收穫就是弄清楚了整個解析器的結構。

在項目的開始階段,咱們首先搭建一個簡單測試框架,好比把測試經過的數量,沒有經過的數量和出錯信息打印出來,方便本身觀察測試經過狀況。

而後須要定義好Json解析器的數據結構,一旦數據結構定義好了,軟件就完成了一半。這裏咱們會用一個樹狀結構來組織咱們解析到的數據,每一個數據保存在一個節點裏,咱們要作的就是把這個節點定義出來。

根據Json協議,Json一共有7種數據類型:

object, array, string, number, "true", "false", "null"

爲了分辨一個節點是哪一種數據類型,咱們須要給節點增長一個type字段,用於標識節點的類型,type的數值咱們能夠用一個枚舉進行維護。同時爲各類數據類型準備一個接收的字段(爲了方便處理,沒有爲true/false/null設置字段)。

type EasyValue struct {
	vType int //節點數據類型
	num   float64
	str   []byte
	len   int
	e     []EasyValue
	o     []EasyObj
}

數據結構搭建好了以後,咱們整個解析器的框架就很清晰了:

  1. 傳入一個Json字符串,建立一個根節點,並用解析器進行解析,具體來講就是逐個字符進行分析。
  2. 假設分析出是一個數字,那麼就把這個根節點的數字類型設置爲數字,並將解析出來的數字放入到節點的num字段中。
  3. 當咱們想要獲取解析的結果時,只須要根據節點的數據類型到節點相應的字段獲取對應的值就行了。

以上就是整個Json解析器的思路了,在第一章腦子裏奠基了這樣的基礎後,就有了總體的大局觀,後面的章節就是根據各類數據類型進行解析。

2. 解析數字

在解析數字的時候,做者選擇了直接調用字符串轉數字的庫函數,因爲庫函數的接收域比較寬,有些錯誤狀況須要咱們提早作處理,整體來講仍是好實現的。

可是在處理的過程當中我卻遇到了一個Go語言中比較棘手的問題:在咱們調用字符串轉數字的庫函數時,是有可能出錯的,一般會有兩種錯誤,一個是數字非法(這個字符串不是一個數字),另外一個是數字溢出。在其餘語言中都可以很好地判斷錯誤類型,而後向用戶端返回相應的錯誤碼。可是Go語言對錯誤的處理比較簡潔,它只提供了一個error接口,接口中只有一個string字段用於說明錯誤信息。這意味着若是一個函數裏同時拋出兩個錯誤,得經過錯誤信息來判斷髮生了什麼錯誤。具體來講就是經過判斷一個字符串中是否包含另外一個字符串來分辨錯誤類型,這彷佛有點土。

f, err := strconv.ParseFloat(convStr, 64)
if err != nil {
	if strings.Contains(err.Error(), strconv.ErrRange.Error()) {
		return EASY_PARSE_NUMBER_TOO_BIG
	}
	return EASY_PARSE_INVALID_VALUE
}

谷歌一番後彷佛仍是沒有特別好的解決方案,現有的開源方案和官方給出的方案基本都是對錯誤進行多一層封裝,但這招好像對庫函數不太管用。也多是我剛開始用go語言,閱歷比較少,在從此的使用中我得留意一下這個問題。

3. 解析字符串 - 4. Unicode

接下來到了解析字符串,在這章被做者的一頓指針操做所折服,可是到了本身實現,發現用Go的Slices彷佛很簡單就實現了,就是不知道性能差得大不大。

在這章最大的收穫是,入門了Unicode編碼。之前編程就是一把梭,編碼這些知識掃兩眼就跳過去了,出了亂碼就谷歌解決方案,沒有考慮過背後的知識。但在這裏得實打實地處理字符的轉換,咱們的目標是把字符串存儲爲UTF-8的形式,背後的關係得搞清楚。

最先的時候用的是ASCII碼,ASCII碼只有7位,也就是隻能表示128個字符。可是世界上的字符太多了,128遠遠不夠,這個時候就出來了Unicode編碼。Unicode編碼記錄了成千上萬個字符,但這也意味着它要更多的存儲空間,Unicode的轉換形式的縮寫就是咱們常見的UTF,而UTF-8就是說把Unicode以8位爲一個單元進行存儲。

有了這些前置知識以後,咱們就須要對字符串中的Unicode編碼進行轉換,具體的過程是把Unicode字符轉換爲對應的碼元(十六進制數),而後把十六進制數編碼成UTF-8的形式。

按照教程作下來對編碼也有了初步的認識,感受良好,這估計就是知識的樂趣吧^_^

5. 解析數組 - 6. 解析對象

到了作解析數組和對象功能時,我感覺到了遞歸的力量,這可能就是做者稱之爲遞歸降低解析器的緣由吧。

可是在這個部分,我最大的收穫是深度體會到了單元測試的好處。當解析數組的時候,咱們極可能須要對多個類型的值進行解析,這個時候就把以前單獨實現的解析功能給串起來了。

好比說這樣一個字符串:

"[123,null,\"abc\",[1,2,3]]"

首先須要解析123,而後解析null,在咱們解析完123的時候,指針應該來到,的位置,經過,進行劃分後再繼續下一個值的解析。記得當時我在解析單個值的時候沒有處理好指針的位置,致使整個數組解析失敗了,不過這也加深了我對整個Json字符串解析過程的理解。

至此整個Json解析器的功能已經基本完成,後面兩個小節是關於生成器和解析對象訪問及其餘功能的。

四. 總結

這個教程是用C語言寫的,做者用了不少C語言的特性,能很好地提升性能,而我剛入門Go語言,對Go的特性瞭解甚少,可能在一些地方沒有用更適合Go語言的處理方式去處理。

而在咱們平常的開發中,一般會這麼用Json:把一個自定義的數據結構轉化成Json串,或者是把Json串轉換爲咱們自定義的結構,目前我尚未實現這樣的功能。而對於這樣的功能,Go語言給予了原生支持。

我看了一下Go原生解析Json的源碼,在解析的思路上和教程是有不少相通之處的。比較大的區別是:在咱們手寫的Json解析器中,咱們把解析後的數據存儲放咱們自定義的節點結構中。而在Go語言中,因爲Json的使用場景經常和結構體相關聯,Go語言會把解析出來的數值經過反射直接賦給相應的結構體,這麼一來省去了自建數據結構的步驟。

最後很是感謝這個教程,讓我對Json的解析有了初步的認識,對測試與重構有了更深的理解,同時也達到了本身的初衷,能熟悉地使用Go語言寫分支循環判斷了。但我知道Go語言的魅力不在於此,還有不少特性等待着我去學習,繼續加油~

相關文章
相關標籤/搜索