天天看點

用 JSON 表現樹的結構兼談隊列、堆棧的練習(一)

接觸 JSON 的人都知道,JSON 可通過 K/V(Key/Value) 結構很直覺地表現一棵樹,因為 V 可以“包含”另外一個 K/V 進而不斷嵌套下去形成“樹狀”的結構。但 V 不一定必須為另外一個 K/V,而是可以為 Array 數組。數組中由可以“包含”更多的 K/V 或者又是數組類型——也是可以的。如此反複下去,可以形成層數很深的一棵樹。例如

這裡說的樹是指無序樹,甚至根節點也沒有,不過沒關系,在最外一層加上便是。

比較微妙的是 JSON 允許了數組和 K/V 互相嵌套。下級子節點應該用 K/V 來裝,還是用 Array 來裝呢?又怎麼了解數組和 K/V 的關系呢?個人了解,數組本質上也可以歸納其為 K/V。我們一般讨論數組時候還會接觸到數組的索引,例如 arr[0] = a, arr[1] = b,索引便是 key 的一種,隻不過我們通常 JSON 裡面的 key 為字元串,實際上為 int 類型也是允許的。相較而言,數組結構比 K/V 的簡單,是 K/V 的一種簡化。當然我這種隻是“大而化之”的了解,——實際上它們差别很多,好比它們的資料結就顯著不同:數組僅僅是一個線性表;K/V 會複雜的多,一般要經過多步 hash 的運算。

再看看上面的 JSON 例子,看起來這個 JSON 想要表達很多東西,最外層是個 K/V,裡面的 aa 是下一層的 K/V,但 bb 卻是數組,——似乎結構有點混亂。如果用來表現一個樹,顯然也是一顆“混亂的樹”。如果實際開發遇到這樣結構的設計,那肯定有問題的,需要好好簡化的。不過,無論怎麼簡化,一旦引入樹的概念後,好像還是會有沖突的地方,例如 K/V 和 Array 兩者都可以延伸一下級節點,它們之間有什麼不同呢?什麼時候該用 K/V 呢?什麼時候又該使用 Array 呢?這并無标準答案,JSON 自身并不會說明清楚或者強制要求。再例如 cc 這個數組,第一個元素是字元串,第二個元素是 K/V。因為我們知道,JSON 包含的元素可允許是不同類型的,即混合多種類型的值為一個數組——那樣本身是沒問題的。但結合樹的概念的話問題就來了,是否 K/V 就必須是引出下級樹節點嗎?——我大可以了解為 JSON 的一個值,她是 K/V 類型,那也是合法的啊,同理數組也不一定引出下級的節點,目前隻是表現同類對象的集合,——那也是完全合法的。是以怎麼定義這是個樹節點,還是說一個 JSON 值?二義性的問題由此産生了。

為解決這個二義性的問題,我們可以對 JSON 作适當的約定,以便更清晰地和準确地反映一棵樹。首先節點用 K/V 表示;Children 是下級節點的數組,是容器。它不能是其他的類型如 map 的類型,隻能是 Array。隻有 最外一層 和 父容器名為 children 的數組,裡面的 K/V 才是樹節點。一個節點可以有零個或一個 children 的 K/V,且 V 必然是數組。

如下便是一個我們限制定義的樹:

值得注意的是該結構最外一層為 Array 而不是 K/V。

JSON 本身乃 JavaScript 的産物,雖然也有序列化和反序列化的過程,但使用起來還是比較自然、“原生原味”的。

這裡重點說說 Java 世界處理 JSON 的話題。當 JSON 字元串經過解析器反序列化之後,可得到 Java 識别的類型。如果引入三方包,就有其自定義的類型(如 JSONArray、JSONObject)。但是我們這裡不使用三方包的類型來說明問題(雖然可能都是“同理”得出一緻的結論),——因為那又牽涉到該使用哪個三方包的問題(選擇困難症患者-_-)。

于 Java 而言與 JSON 對應的結構一般自然的選擇是 Map/List 組合——本文就拿 Map/List 就好了。這裡用泛型可以加強說明所包含元素的類型是什麼,使之更加直覺和清晰,即 Map,其中,String 是 key 的類型,我們知道 JSON 的key 類型就是字元串類型;Object 便是 Value 的類型,可以是合法的 JSON 值(字元串、數字、null),或者是另外一個 Map 或 List。至于 List 的泛型便是嵌套的 List<Map>。于是,我們可以寫一個方法(或者第三方包),JSON 字元串被解析之後,得到 Map/List 結構的 Java 類型,變成 Java 可以了解的“樹”。應該怎麼周遊的這棵樹呢?最簡單的方法,莫過于遞歸這個 Map/List。

好比現在輸入這段 JSON,這是網站的配置檔案:

送入 JSON 解析器得到 Map:

用 JSON 表現樹的結構兼談隊列、堆棧的練習(一)

這裡暫忽略 JSON 解析器的原理。先接着看看周遊 JSON 的過程。假設我們要把所有 key 列出來

列印結果如下

用 JSON 表現樹的結構兼談隊列、堆棧的練習(一)

前面說到,我們讨論的是樹結構,已經有這樣的約定:如果遇到 Key 為 children 且 value 為數組元素的話,那就下級節點,數組裡的都是子節點 K/V。否則就是普通的一個 JSON 數組。

與前面的函數相比隻是增加了 children 的判斷,然後周遊 children 裡面各項的 map。——一切都非常簡單是吧?可以說毫無驚豔之處。不過讀者可試着改造一下,把目前支援 Map<String, Object> map 類型的參數改為List<Map<String, Object>> list 的,看看周遊過程有什麼不同。

現在不妨把需求的難度提高那麼一丢丢:希望可以完整記下節點的完整的“路徑”和層級。文章到這裡寫得太長太冗長了,筆者還是趕緊給出代碼,趕緊收尾。

輸入 JSON 數組:

這裡給出前一小節的答案,就是周遊 List 的,并增加了功能。

結果是

用 JSON 表現樹的結構兼談隊列、堆棧的練習(一)

好吧,我承認,這也不是太難的例子,仍然不外乎 for 循環+ 遞歸。下一篇要寫 Stack 的内容了。