1. 程式人生 > 其它 >前端開發系列023-基礎篇之JavaScript和JSON(擴充套件)

前端開發系列023-基礎篇之JavaScript和JSON(擴充套件)

title: '前端開發系列023-基礎篇之JavaScript和JSON(擴充套件)'
tags:
  - javaScript系列
categories: []
date: 2017-06-20 08:20:13
本文輸出JSON搜尋和JSON轉換相關的內容,是對前兩篇文章的補充。

一、JSON搜尋

在特定的開發場景中,如果伺服器端返回的JSON資料異常複雜(可能超過上萬行),那麼必然就有對JSON文件進行搜尋的需求。在對JSON文件進行搜尋的時候,建議使用專業的JSON搜尋類庫和工具來實現,這可以極大的簡化JSON文件搜尋的工作並降低工作難度。

JSON搜尋的具體適用場景:對於某次API呼叫,我們只需要其中的部分資料,這種情況我們就可以根據某個標準來對返回的JSON內容進行搜尋和過濾。

本文將會先後介紹多款處理JSON搜尋的類庫(工具),包括但不限於 JSONPathJSON Pointerjq等,在對各種方案進行介紹的時候將會從方案的優缺點、具體的使用方式等角度切入,開發中可以根據實際的開發場景和各工具自身的特點來進行選擇。

工具001 → jq

jq    是一個提供了命令列介面的JSON搜尋工具。

功能 可使用自身特定的查詢語法來過濾JSON和擷取陣列,類似於JSON中的sed。

生態 除命令列外,擁有優秀的基於Web的jq測試器,甚至Node社群還以npm模組形式釋出了教程

優勢

- 提供豐富的搜尋和過濾功能。
- 大多數程式語言都對jq提供良好的支援。
- jq相關文件質量較高,且擁有友好的互動式教程。
- 擁有優秀的[線上測試工具](https://jqplay.org/),能夠對查詢提供快速反饋。
- 在命令列中能夠很好的與cURL以及管道操作等協同工作。
- 使用C語言編寫的,沒有執行時依賴,可執行在Linux,OS X和Windows等平臺。

資料 官網Github倉庫Ubuntu-jq手冊Hyperpolyglot JSON工具Jackson類庫Ruby-jq gem

語法

  ======基本語法======
  .     輸出所有的文件內容。
  |     管道符,傳遞資料流。
  .key  輸出指定key對應的部分文件。
  .[ ]  輸出陣列中指定索引對應的元素。

  ======查詢語法示例========
  .person[0]	獲取JSON文件person陣列中的第一個元素。
  .person[-1]	獲取JSON文件person陣列中的最後一個元素。
  .person[0:3]	獲取JSON文件person陣列中的前面三個元素。
  .person[] | select (.age>20 ) 獲取JSON文件中滿足要求(age > 20)的所有資料。

安裝

  • OSX系統 建議使用Homebrew來安裝,具體命令為:$ brew install jq
  • windows系統 建議使用Chocolatey NuGet來安裝,具體命令為: $ chocolatey install jq
  • 當然也可以通過git clone倉庫原始碼來進行安裝,具體細節以及其它系統處理請參考Download jq

這裡給出OSX系統中通過命令列安裝jq的具體細節和示例。

wendingding$ brew install jq
Updating Homebrew...
==> Installing dependencies for jq: oniguruma
==> Installing jq dependency: oniguruma
==> Downloading https://homebrew.bintray.com/bottles/oniguruma-6.8.2.high_sierra
######################################################################## 100.0%
==> Pouring oniguruma-6.8.2.high_sierra.bottle.tar.gz
/usr/local/Cellar/oniguruma/6.8.2: 17 files, 1.2MB
==> Installing jq
==> Downloading https://homebrew.bintray.com/bottles/jq-1.5_3.high_sierra.bottle
######################################################################## 100.0%
==> Pouring jq-1.5_3.high_sierra.bottle.tar.gz
/usr/local/Cellar/jq/1.5_3: 19 files, 946.6KB

wendingding$ jq --version
jq-1.5
在安裝jq的時候,如果命令列報Error: Failure while executing: git config --local --replace-all homebrew.private true錯誤,可以嘗試先執行`$ xcode-select --install `命令然後重新安裝。在安裝的時候如果總是卡在Updating Homebrew...可以`control + C`停止更新。

工具(jq-tutorial)

jq-tutorial是node社群以npm模組的形式釋出的jq教程,是學習jq使用的一個比較好用的工具,這裡簡單列出該模組的安裝和使用示例,並對命令進行簡單的說明。

wendingding$ npm search jq-tutorial
NAME                      | DESCRIPTION          | AUTHOR          | DATE       
jq-tutorial               | Exercises for…       | =rjz            | 2016-09-29 

wendingding$ npm install -g jq-tutorial
npm WARN notice [SECURITY] lodash has the following vulnerability: 1 low. 
Go here for more details: https://nodesecurity.io/advisories?search=lodash version=2.4.2 
-Run `npm i npm@latest -g` to upgrade npm version, and then `npm audit` to get more info.
/usr/local/bin/jq-tutorial -> /usr/local/lib/node_modules/jq-tutorial/bin/jq-tutorial
+ [email protected]
added 4 packages in 20.012s

wendingding$ jq-tutorial 
Run jq-tutorial with one of the following:
  * pick
  * objects
  * mapping
  * filtering
  * output
  * reduce

wendingding$ jq-tutorial pick
Pick
========================================
### Pick fields from an object
`jq` retrieves named properties from objects by using `.` syntax:
    $ echo '{"foo": { "bar": "a value" }}' | jq .foo
Nested values are accessible as well:
    $ echo '{"foo": { "bar": "a value" }}' | jq .foo.bar
### Pick elements from an array:
Elements in an array may be extracted by index:
    $ echo '["snap","crackle","pop"]' | jq .[1]
More than one index? No problem!
    $ echo '["snap","crackle","pop"]' | jq .[1, 2]
We can even extract *all* elements at once by omitting the indices:
    $ echo '["snap","crackle","pop"]' | jq .[]
type "data?" to see dataset or "help?" for more options
--------------------------------

Given:    'product' (type "data?" to view)
Challenge: Select the entire item (hint: don't overthink this!):
> 

工具(jqPlay)

jqPlay是一個基於web的jq線上測試遊樂場,它提供了對JSON資料進行jq查詢的基本功能,而且提供了簡單的jq查詢語法示例,能夠對查詢進行快速反饋。

基本用法演示

這裡我先提供一個稍複雜的JSON資料,資料儲存在/JSON-Demo/data.json路徑(您可以點選此連結獲取該資料)。為了演示方便,這裡我將會把該文件的資料部署為RESTful API,從而建立一個模擬的API服務。在具體的處理中,將使用到名為json-server的Node模組,下面列出具體的細節。

wendingding$ pwd
/Users/文頂頂/Desktop/JSON-Demo

wendingding$ npm install -g json-server
/usr/local/bin/json-server -> /usr/local/lib/node_modules/json-server/bin/index.js
+ [email protected]
added 223 packages in 23.03s

wendingding$ json-server -p 5000 ./data.json

  \{^_^}/ hi!

  Loading ./data.json
  Done

  Resources
  http://localhost:5000/person

  Home
  http://localhost:5000

  Type s + enter at any time to create a snapshot of the database
GET /person 304 16.355 ms - -

執行$json-server -p 5000 ./data.json命令之後,我們可以在瀏覽器中通過http://localhost:5000/person地址來訪問JSON文件中的資料,下面是顯示結果。

備註:如果經常需要通過瀏覽器訪問和顯示JSON資料,建議安裝相應的JSON擴充套件外掛,我電腦Chrome安裝的是JSON-handle,同類型的還有JSONView

下面列出jq命令列工具的使用示例以及主要命令列的解讀說明。

wendingding$ curl http://localhost:5000/person | jq '.'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   169k      0 --:--:-- --:--:-- --:--:--  255k
[
  {
    "age": 18,
    "name": "mitaoer",
    "hasBrother": true,
    "email": "[email protected]",
    "interest": [
      "文學",
      "音樂",
      "繪畫",
      "IT"
    ],
    "car": {
      "type": "英菲尼迪",
      "number": "京A 000001",
      "price": 200012.66
    }
  },
  {
    "age": 28,
    "name": "wendingding",
    "hasBrother": true,
    "email": "[email protected]",
    "interest": [
      "文學",
      "音樂"
    ],
    "car": {
      "type": "奧迪",
      "number": "京A 000002",
      "price": 200000.66
    }
  },
  {
    "age": 23,
    "name": "xiaxiaoxia",
    "hasBrother": true,
    "email": "[email protected]",
    "interest": [
      "文學",
      "IT"
    ],
    "car": null
  },
  {
    "age": 24,
    "name": "LiuY",
    "hasBrother": true,
    "email": "[email protected]",
    "interest": [
      "文學",
      "音樂",
      "繪畫",
      "IT",
      "閱讀",
      "健身"
    ],
    "car": {
      "type": "ATS",
      "number": "京A 000003",
      "price": 888888.66
    }
  }
]

wendingding$ curl http://localhost:5000/person | jq '.[1]'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   140k      0 --:--:-- --:--:-- --:--:--  255k
{
  "age": 28,
  "name": "wendingding",
  "hasBrother": true,
  "email": "[email protected]",
  "interest": [
    "文學",
    "音樂"
  ],
  "car": {
    "type": "奧迪",
    "number": "京A 000002",
    "price": 200000.66
  }
}

wendingding$ curl http://localhost:5000/person | jq '.[1].name'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   171k      0 --:--:-- --:--:-- --:--:--  255k
"wendingding"

wendingding$ curl http://localhost:5000/person | jq '.[1].email'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   106k      0 --:--:-- --:--:-- --:--:--  127k
"[email protected]"

wendingding$ curl http://localhost:5000/person | jq '.[1] | {name,age}'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   161k      0 --:--:-- --:--:-- --:--:--  204k
{
  "name": "wendingding",
  "age": 28
}

wendingding$ curl http://localhost:5000/person | jq '.[1] | {newName:.name,newAge:.age}'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   153k      0 --:--:-- --:--:-- --:--:--  204k
{
  "newName": "wendingding",
  "newAge": 28
}

wendingding$ curl http://localhost:5000/person | jq '.[] | select (.age >=24)'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   167k      0 --:--:-- --:--:-- --:--:--  255k
{
  "age": 28,
  "name": "wendingding",
  "hasBrother": true,
  "email": "[email protected]",
  "interest": [
    "文學",
    "音樂"
  ],
  "car": {
    "type": "奧迪",
    "number": "京A 000002",
    "price": 200000.66
  }
}
{
  "age": 24,
  "name": "LiuY",
  "hasBrother": true,
  "email": "[email protected]",
  "interest": [
    "文學",
    "音樂",
    "繪畫",
    "IT",
    "閱讀",
    "健身"
  ],
  "car": {
    "type": "ATS",
    "number": "京A 000003",
    "price": 888888.66
  }
}

wendingding$ curl http://localhost:5000/person | jq '.[1,3] | {name,email}'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   162k      0 --:--:-- --:--:-- --:--:--  255k
{
  "name": "wendingding",
  "email": "[email protected]"
}
{
  "name": "LiuY",
  "email": "[email protected]"
}

wendingding$ curl http://localhost:5000/person | jq '.[1,3] | [{name,email}]'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   165k      0 --:--:-- --:--:-- --:--:--  255k
[
  {
    "name": "wendingding",
    "email": "[email protected]"
  }
]
[
  {
    "name": "LiuY",
    "email": "[email protected]"
  }
]
wendingding$ touch test.json

wendingding$ curl http://localhost:5000/person | jq '.[1,3] | [{name,email}]' > test.json
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1047  100  1047    0     0   143k      0 --:--:-- --:--:-- --:--:--  204k
wendingding$ cat test.json 
[
  {
    "name": "wendingding",
    "email": "[email protected]"
  }
]
[
  {
    "name": "LiuY",
    "email": "[email protected]"
  }
]

主要命令說明[為方便閱讀,命令列中的xxx均代表的是http://localhost:5000/person路徑]

$ curl xxx | jq '.' 獲取API返回的所有JSON資料。

$ curl xxx | jq '.[1]' 獲取JSON文件中person陣列的第2個元素內容。

$ curl xxx | jq '.[1].name' 獲取JSON文件中person陣列的第2個元素(物件)中的name屬性值。

$ curl xxx | jq '.[1] | {name,age}' 獲取陣列第2個元素(物件)的name和age鍵值對組成新物件。

$ curl xxx | jq '.[1,3] | {name,email}' 獲取陣列第2和第4個元素中的name和email值組成物件。

$ curl xxx | jq '.[] | select (.age >=24)' 獲取JSON文件中所有age屬性值>=24的物件元素集合。

補充 如果需要在Node中使用jq,那麼可以安裝並使用require匯入node-jq模組。

工具002 → JSONPath

定位 是一款可用於對JSON文件進行搜尋和資料提取操作類庫。

歷史Stefan Goessner於2007年開發,最開始的版本使用JavaScript實現。

功能 能夠對JSON文件進行搜尋和資料提取,它的查詢語法基於XPath實現。

生態 JSONPath沒有提供命令列操作實現,但提供了優秀的線上測試工具Node模組

優勢

- 具有豐富的查詢語法。
- 查詢語句可以返回文件中的多個元素。
- 大多數的主流平臺都對JSONPath提供支援。
- 擁有很高的社群使用率,優秀的線上測試工具對開發者更友好。

資料 jsonpath Node模組線上測試網站Github倉庫Stefan Goessner主頁

語法 描述
$ 根節點
@ 當前節點的篩選器屬性處理
* 萬用字元,匹配任何屬性名稱
.. 萬用字元,匹配任意層次的節點
[] 迭代器標示,同陣列索引
[,] 迭代器標示,迭代器多選
[start:end] 陣列切片運算子
[?(<expression>)] 過濾表示式,求值必須為布林值

查詢語法示例

//備註:參考的JSON資料為前文使用的data.json檔案
$                       獲取整個JSON文件的內容
$.person                獲取JSON文件中person陣列的內容
$.person.length         獲取JSON文件中person陣列的長度(元素個數)

$.person[0]             獲取JSON文件中person陣列第一個元素的內容
$.person[:1]            獲取JSON文件中person陣列第一個元素的內容
$.person[-1:]           獲取JSON文件中person陣列最後一個元素的內容
$.person[(@.length-1)]  獲取JSON文件中person陣列最後一個元素的內容

$.person[:2]            獲取JSON文件中person陣列前兩個元素的內容
$.person[0,3]           獲取JSON文件中person陣列第1和4第個元素的內容
$.person[0::2]          獲取JSON文件中person陣列指定元素的內容(隔一個元素抽取)
$.person[:2].name       獲取JSON文件中person陣列前兩個元素中的name值

$..name                 獲取JSON文件中所有的name子元素內容
$.person[?(@.age >23)]  獲取JSON文件中age值大於23的所有元素
$.person[?(@.age >23)].age  獲取JSON文件中age值大於23的所有元素中的age值資訊
$.person[?(@.age >20 && @.interest.length == 2)].name 滿足多個條件的篩選

工具(jsonpath線上測試網站)

jsonpath提供了對應的線上測試網站,給指定JSON文件輸入對應的jsonpath查詢語句能夠快速看到最終效果。使用該測試工具來可以"重量級的"複雜JSON資料進行快速的篩選,輸入查詢語句後馬上就能夠在右側看到查詢後的結果。如果開發者原本不瞭解查詢語法,那也可以通過該工具來快速的學習,下面給出簡單的圖示。

jsonpath線上測試工具在使用的時候,總是會把查詢的結果儲存到[ ]的結構中。

工具(node模組jsonpath)

JSONPath本身沒有命令列工具,除了上面介紹的線上測試網站之外,我們還能在程式碼中使用node社群釋出的jsonpath模組實現JSON的搜尋任務。下面給出一個簡單的單元測試示例(列出原始碼和執行情況)。

001 先列出單元測試相關的程式碼

//jsonpath-test.js檔案內容
var unirest  = require("unirest");
var jsonPath = require("jsonpath");
var expect   = require("chai").expect;

describe("wendingding-test",function(){
    var request ;
    beforeEach(function(){
        request = unirest.get("http://localhost:5000/person")
        .header("Accept","application/json");
    })

    it("return 200 狀態碼",function(done){
        request.end(function(response){
            expect(response.statusCode).to.eql(200);
            expect(response.headers["content-type"])
            .to.eql("application/json; charset=utf-8");
            done();
        })
    })

    it("return 所有的JOSN資料",function(done){
        request.end(function(response){
            expect(response.body.length).to.eql(4);
            done();
        })
    })

    it("return 所有的JOSN資料中第一個元素      -- $[1]",function(done){
        request.end(function(response){
            var resultData = jsonPath.query(response.body,"$[1]");
            expect(resultData[0].name).to.eql("wendingding");
            done();
        })
    })

    it("return 所有的JOSN資料中最後一個元素[1] -- $[-1:]",function(done){
        request.end(function(response){
            var resultData = jsonPath.query(response.body,"$[-1:]");
            expect(resultData[0].email).to.eql("[email protected]");
            done();
        })
    })

    it("return 所有的JOSN資料中最後一個元素[2] -- $[(@.length-1)]",function(done){
        request.end(function(response){ 
            var resultData = jsonPath.query(response.body,"$[(@.length-1)]");
            expect(resultData[0].email).to.eql("[email protected]");
            done();
        })
    })

    it("return 所有的JOSN資料中滿足條件元素     -- $[?(@.age >= 23)]",function(done){
        request.end(function(response){
            var data = response.body;  
            var resultData = jsonPath.query(data,"$[?(@.age >= 23)]");
            //console.log(resultData)
            expect(resultData.length).to.eql(3);
            for(var i = 0 ;i<resultData.length;i++)
            {
                expect(resultData[i].age).to.be.at.least(23);
            }
            done();
        })
    })
})

002 列出程式碼的具體執行細節

wendingding$ pwd 
/Users/文頂頂/Desktop/jsonPath-demo
wendingding$ npm test

> [email protected] test /Users/文頂頂/Desktop/jsonPath-demo
> mocha



  wendingding-test
    ✓ return 200 狀態碼
    ✓ return 所有的JOSN資料
    ✓ return 所有的JOSN資料中第一個元素      -- $[1]
    ✓ return 所有的JOSN資料中最後一個元素[1] -- $[-1:]
    ✓ return 所有的JOSN資料中最後一個元素[2] -- $[(@.length-1)]
    ✓ return 所有的JOSN資料中滿足條件元素    -- $[?(@.age >= 23)]


  6 passing (92ms)

003 程式碼說明

〇 示例程式碼中使用了MochaUnirest測試框架,jsonpath查詢模組以及Chai模組中的斷言結構。

① 示例程式碼中的每一個it就代表著一個測試用例。

② 示例程式碼中我們在Mocha的beforeEach()方法中對請求資訊進行了配置。

③ 示例程式碼中在describe語句定義的範圍內,每次執行測試用例之前都會先執行一次beforeEach方法。

004 執行備註

這裡簡單說明上面程式碼的執行環境和處理過程。

[1]  在電腦中指定的路徑建立資料夾,並通過命令列進入到該路徑。

$ mkdir JSON-TEST
$ cd JSON-TEST/
$ pwd
    /Users/文頂頂/Desktop/JSON-TEST

[2] 初始化並安裝必要的node模組。

$ npm init  //預設回車即可
$ npm install -g mocha
$ npm install --save-dev mocha
$ npm install --save-dev unirest
$ npm install -save-dev jsonpath
$ npm install --save-dev chai

[3] 修改package.json檔案中的scripts項為"test": "mocha"。

wendingding$ cat package.json 
{
  "name": "json-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "mocha"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chai": "^4.2.0",
    "jsonpath": "^1.0.0",
    "mocha": "^5.2.0",
    "unirest": "^0.5.1"
  }
}

[4] 在當前目錄下建立test資料夾,並在該資料夾中建立測試檔案(此處命名為json-test.js)。

$ mkdir test
$ cd test/
$ touch json-test.js

[5] 編輯json-test.js檔案的內容(前文已經給出),列出檔案目錄。
.
├── node_modules
│   ├── JSONSelect
│   ...省略
│   ├── chai
│   ├── jsonpath
│   ├── mocha
│   ├── unirest
│   └── xtend
├── package-lock.json
├── package.json
└── test
    └── json-test.js

[6] 執行測試。

$ npm test

工具003 → JSON Pointer

JSON Pointer本身是一項用於獲取JSON文件中特定值的標準。JSON Pointer設計的主要目標在於支援JSON Schema標準中的$ref(請參考JSON進階一文)。

目前,大多數的主流平臺(包括Node\Ruby on Rails\Ptyhon\Java等)都已經包含JSON Pointer相關的類庫。Java,Jackson已支援JSON Pointer查詢語法,javaEE 8將提供原生支援;Node中則可以在社群中找到並使用json-pointer模組來對JSON文件進行處理。

語法 描述
/data 獲取JSON文件中某個key對應的所有資料
/data/index 獲取指定索引對應的資料
/data/index/key 獲取指定索引對應的資料並通過key來取值

JSON Pointer的查詢語法簡潔高效,其工作機制是以/來表示路徑分隔,以索引|下標來獲取指定的內容,索引總是從0開始。下面給出簡短的查詢語法示例:

//備註:參考的JSON資料為前文使用的data.json檔案
/person                 獲取JSON文件中person的內容
/person/0               獲取JSON文件中person陣列的第一項內容
/person/0/name          獲取JSON文件中person陣列的第一項內容中的name值
JSON Pointer標準中,查詢操作的結果總只包含資料值而不會包含相關的鍵名。

這裡將使用node社群的json-pointer模組簡單演示node平臺中對JSON文件的處理。下面列出核心單元測試程式碼(在測試的時候需要先安裝對應的模組)。

var expect = require("chai").expect;
var pointer = require("json-pointer");
var unirest = require("unirest");

describe("wendingding-test",function(){
    var request ;
    beforeEach(function(){
        request = unirest.get("http://localhost:5000/person")
        .header("Accept","application/json");
    })

    it("return 200 狀態碼",function(done){
        request.end(function(response){
            expect(response.statusCode).to.eql(200);
            expect(response.headers["content-type"])
            .to.eql("application/json; charset=utf-8");
            done();
        })
    })

    it("return 所有的JOSN資料",function(done){
        request.end(function(response){
            expect(response.body.length).to.eql(4);
            done();
        })
    })

    it("return Person陣列中的第一個元素 /person/0",function(done){
        request.end(function(response){
            var resultData = pointer.get(response.body,"/0");
            console.log("\n",resultData,"\n");
            expect(resultData.name).to.eql("mitaoer");
            done();
        })
    })

    it("return Person陣列中的第三個元素中interest的值 /person/2/interest",function(done){
        request.end(function(response){
            var resultData = pointer.get(response.body,"/2/interest");
            console.log("\t",resultData);
            expect(resultData.length).to.eql(2);
            done();
        })
    })
})

簡單列出執行情況:

wendingding$ npm test
> [email protected] test /Users/文頂頂/Desktop/JSON-Pointer
> mocha

  wendingding-test
    ✓ return 200 狀態碼
    ✓ return 所有的JOSN資料

      { age: 18,
        name: 'mitaoer',
        hasBrother: true,
        email: '[email protected]',
        interest: [ '文學', '音樂', '繪畫', 'IT' ],
        car: { type: '英菲尼迪', number: '京A 000001', price: 200012.66 } 
      } 
    ✓ return Person陣列中的第一個元素 /person/0
	    [ '文學', 'IT' ]
    ✓ return Person陣列中的第三個元素中interest的值 /person/2/interest

  4 passing (66ms)

二、JSON轉換

JSON → HTML結構(渲染)

將JSON資料轉換(處理)為HTML的操作我們應該都很熟悉,在前端開發和移動端開發領域中,這一部分的操作通常和網路請求緊密聯絡,業務流程基本都是先發請求獲取伺服器端返回的(JSON)資料,然後通過序列化的方法來對資料進行解析,也就是反序列化處理(通常是將JSON資料轉換為程式語言中對應的資料結構比如陣列或者是物件),最終再根據得到的資料來更新UI。

現在前端開發中這都屬於基本操作,甚至像Vue這樣類似的框架中資料繫結已經是其最最基礎的一部分了。雖然如此,為了文章的完整性,這裡還是會簡單介紹MustacheHandlebars兩個類庫在JSON轉換中的運用。

Mustache

簡介 Mustache使用宣告式模板來轉換資料格。
資料 MustacheMustache GithubMustache 5說明文件
優勢 使用模板可以從程式碼中抽取具體的資料資訊,並將資料儲存在外部的檔案中,實現關注點分離。

接下來我將通過一個簡短的示例來說明Mustache的語法以及其使用方式,您可以點選該連結獲取完整的專案內容。為了對介紹Mustache的使用,這裡我列出專案中的部分內容並做簡要說明。

001 列出模板核心內容

//備註:../Tem-TEST/src/index.mustache檔案的核心內容
<div id="app">
    <table id="tb">
        <tr>
            <th>name</th>
            <th>age</th>
            <th>email</th>
            <th>interest</th>
            <th>car-type</th>
            <th>car-number</th>
        </tr>
        {{#person}}
        <tr>
            <td>{{name}}</td>
            <td>{{age}}</td>
            <td>{{email}}</td>
            <td>{{interest.1}}</td>
            {{#car}}
                <td>{{type}}</td>
                <td>{{number}}</td>
            {{/car}}
        </tr>
        {{/person}}
    </table>
</div>

002 列出單元測試程式碼

//備註:../Tem-TEST/test/mustache-test.js檔案內容
var fs = require("fs");
var expect = require("chai").expect;
var jsonfile = require("jsonfile");
var mustache = require("mustache");

describe("wendingding-mustache-test",function(){
  
    //檔案的目錄結構:json檔案路徑 + 模板檔案路徑 + 目標檔案路徑
    var jsonFileFullPath     = __dirname + "/../src/data.json";
    var templateFileFullPath = __dirname + "/../src/index.mustache";
    var targetFileFunllPath  = __dirname + "/../src/index.html";

    it("JSON -> HTML",function(done){
        jsonfile.readFile(jsonFileFullPath,function(readJsonFileError,jsonData){
            if(!readJsonFileError)
            {
                fs.readFile(templateFileFullPath,"utf8",function(readTemplateFileError,templateData){
                    if(!readTemplateFileError)
                    {
                        var template = templateData.toString();
                        var html = mustache.render(template,jsonData);

                        fs.writeFile(targetFileFunllPath, html,  function(errorStatus) {
                            if (!errorStatus) {
                                console.log("轉換成功並儲存為HTML檔案!");
                                done();
                            }else
                            {
                                done(errorStatus);
                            }  
                         }); 
                    }else
                    {
                        done(readTemplateFileError)
                    }
                })
            }else
            {
                done(readJsonFileError)
            }
        })
    })
})

通過$ npm test執行單元測試程式碼,將會執行JSON陣列到HTML的轉換,處理完畢後結果儲存到index.html檔案中,下面貼出該頁面的效果圖。

Mustache模板工作機制

> ❐  模板基於HTML,  Mustache使用JSON資料來解析標籤。
> ❐  模板中的標籤可以表示單個欄位,使用`雙大括號`的形式來包裹。
> ❐  模板中的區塊都需要由`開始標籤和結尾標籤`組成,例如上例中的person。
> ❐  模板中的區塊對應JSON資料中的陣列或者是物件,例如上例中的person和car。
> ❐  模板中的區塊可以為內部的標籤定義上下文,比如car區塊內部的type和number。

Mustache工具(命令列 && 線上網站)

Mustache除上面演示的使用方式之外,還能直接在命令列中使用,下面給出簡短的使用示例。

(1) 全域性安裝Mustache模組。
$ npm install -g mustache 

(2) 執行mustache命令轉換。
$ mustache /Users/文頂頂/Desktop/Tem-TEST/src/data.json /Users/文頂頂/Desktop/Tem-TEST/src/index.mustache > target.html

$ open target.html

這裡再推薦一款好用的線上模板編輯器Architect,使用該工具可以有效的簡化測試和開發模板的工作,當修改模板的時候,可以實時的看到渲染的結果。該網站支援多款主流模板引擎(包括doT.js、Dustjs、EJS、Handlebars.js、Hogan.js、Jade、Mustache、Nunjucks和Underscore.js等)的編輯和渲染,可以有效的加速開發和除錯工作。

Handlebars

簡介 Handlebars是Mustache的擴充套件,使用hash或物件來渲染模板中的標籤。

資料 HandlebarsHandlebars GithubArchitect線上測試網站Handlebars的Node模組

說明 Handlebars與Mustache高度相容,相對而言Handlebars自身增加了一些特性來增強轉換操作。它們的差異主要在於Handlebars提供了ifunless等內聯的輔助語句且允許開發者通過註冊自定義輔助語義的方式來進行擴充套件,功能更加強大。

優勢

- 模板語言豐富,能滿足大多數的轉換需求。
- 擁有優秀的線上編輯和測試工具用起來更加方便。
- 採用宣告式,但也支援在自定義輔助指令中編寫邏輯程式碼。
- 因為擁有內建的條件邏輯,所以在渲染的時候幾乎可以不用編寫額外的處理程式碼。
- 跨平臺的支援度很好,支援`JavaScript`、`Node.js`、`Java`和`Ruby on Rails`等平臺。

這裡將簡單介紹Handlebars在Node中的使用,並提供node和命令列兩種執行示例供參考,更多的細節請自行參考其官網文件。

001 列出模板檔案的核心程式碼

//備註(1):/Users/文頂頂/Desktop/Handlebars-Test/index.hbs檔案的核心內容
//備註(2):轉換過程中使用的json資料為前文中的data.json檔案
<div id="app">
    <table id="tb">
        <tr>
            <th>name</th>
            <th>age</th>
            <th>email</th>
            <th>car-type</th>
            <th>car-Other</th>
        </tr>
        {{#each person}}
        {{#if car}}
        <tr>
            <td>{{name}}</td>
            <td>{{age}}</td>
            <td>{{email}}</td>
            <td>{{car.type}}</td>
            <td>號碼:{{car.number}}  |  價格:{{car.price}}</td>
        </tr>
        {{/if}}
        {{/each}}
    </table>
</div>

002 列出命令列執行的細節

(1) 先通過命令列全域性安裝hb-interpolate模組
$ npm install -g hb-interpolate

(2) 執行渲染命令。
$ hb-interpolate -j /Users/文頂頂/Desktop/Handlebars-Test/data.json -t  /Users/文頂頂/Desktop/Handlebars-Test/index.hbs > target.html

說明 上面的命令列中-j表示後面跟的是json檔案,-t表示後面跟的是模板檔案,轉換後的結果被輸出並儲存到target.html檔案中。在模板檔案中的#each表示遍歷陣列,#if是邏輯控制指令,在渲染的時候過濾了car為null的資料情況,瀏覽器開啟target.html檔案可以看到下面的顯示結果。

003 Handlebars在Node中的使用

//備註:handlebars-test.js檔案的內容
//001 匯入node模板
var fs          = require("fs");
var jsonfile    = require("jsonfile");
var handlebars  = require("handlebars");

//002 處理檔案路徑
var jsonFullPath     = __dirname + "/data.json";
var templateFullPath = __dirname + "/index.hbs";
var outPutFullPath  = __dirname + "/output.html";

//003 讀取JSON檔案的內容
jsonfile.readFile(jsonFullPath,function(readJsonError,jsonData){
    if(!readJsonError)
    {
        //004 讀取模板檔案的內容
        fs.readFile(templateFullPath,"utf8",function(readTemplateError,templateData){
            if(!readTemplateError)
            {
                //005 JSON資料 + 模板 = > 渲染
                var template = handlebars.compile(templateData);
                var html = template(jsonData);

                //006 把渲染後的結果儲存到指定檔案中
                fs.writeFile(outPutFullPath, html,  function(errorStatus) {
                    if(! errorStatus)
                    {
                        console.log("渲染成功!請開啟"+outPutFullPath+"檢視結果!");
                    }
                })
            }
        })
    }
})

說明 在指定檔案目錄中(我這裡是/Users/文頂頂/Desktop/Handlebars-Test)建立handlebars-test.js檔案,並編寫上述對應的程式碼。通過命令列安裝必要的Node模組,執行即可得到前文所示的圖片結果。下面給出命令列執行的細節。

(1) 切換到當前目錄
$ cd Handlebars-Test/
$ pwd 
/Users/文頂頂/Desktop/Handlebars-Test

(2) 使用npm初始化並安裝必要的Node模組。
$ npm init
$ npm install --save-dev jsonfile
$ npm install --save-dev handlebars

(3) 執行。
$ node handlebars-test.js

補充 Mustache和Handlebars除用來把JSON轉換為HTML(渲染)之外,還能夠對JSON資料本身的格式進行轉換工作(主要是對JSON資料進行二次處理,譬如刪減或結構調整等),但Mustache在具體進行格式化的時候因為無法確定當前所處理的元素是否為陣列或物件的最後一個元素,所以可能存在"無謂逗號"的問題。Handlebars可以通過使用#unless 和@last的形式對"無謂逗號"的問題進行規避,具體的細節請參考其官方文件說明。另外,JSON格式轉換的工具在Node環境中推薦使用Json2Jsonjsonapter,也可以參考JSON PatchJSON-T的實現,這裡不再展開。

JSON資料 ←→ XML文件

最後簡單介紹JSON資料和XML資料之間的相互轉換,雖然這種場景通常可能很少出現(因為開發中常見的場景一般是對JSON或XML資料進行序列化或反序列化處理,JSON和XML兩種資料格式之間直接相互轉換的情況真的很少見)。

XML全稱Extensible Markup Language(可擴充套件標記語言),主要流行於(1998~2008年)。同JSON類似,XML也能用於表示和傳輸資料,現在很多大公司的API都提供XML和JSON兩種格式的資料響應。

XML資料和JSON資料的轉換難易程度主要看XML文件的結構,如果文件中所有的資料都以XML元素和文字的方式儲存那麼轉換為JSON資料是比較簡單的,如果XML文件的元素上存在這大量的屬性節點(早年的時候很多XML的設計人員把資料儲存在XML的屬性節點上,這樣有助於減少檔案體積和簡化多平臺之間的轉換工作),那麼這種轉換就會比較困難。不過,好在我們可以使用很多現成的工具(譬如:Parker和JsonML以及Badgerfish等)來完成這種具體的轉換工作。

對於上面這些工具的具體使用情況,大家可以自行了解。需要說明的是即便如此,這些工具仍然存在很大的侷限性(譬如文件不全缺乏跨平臺的支援完整實現以及有損轉換等等)。所以,在實際的使用過程中其實可以考慮先把JSON|XML轉換為當前程式語言中的資料結構形式,然後再轉換成XML|JSON)。下面以JavaScript(Node)平臺為例加以簡單說明。

XML文件 → JSON資料

  • 先把XML資料解析為JavaScript中的物件|陣列(xml2js模組)。
  • 把JavaScript的物件|陣列序列化為JSON格式的資料(JSON.stringify方法)。

列出核心示例程式碼

var xmlFullPath      = __dirname + "/data.xml";
fs.readFile(xmlFullPath,"utf8",function(readFileError,xmlData){
    var parser = new xml2js.Parser();
    parser.parseString(xmlData,function(error,xmlObj){
        console.log(JSON.stringify(xmlObj,null,2));
    })
})

JSON資料 → XML文件

  • 先將JSON資料解析(反序列化)為JavaScript中的物件|陣列資料(eval函式或者是JSON.parse方法)。
  • 根據JavaScript資料來生成(marshaling)對應的XML文件(xml2js模組)

這裡列出json資料轉換為xml資料的程式碼示例。

//001 匯入node模板
var fs          = require("fs");
var xml2js      = require("xml2js");
var jsonfile    = require("jsonfile");

//002 處理檔案路徑
var jsonFullPath     = __dirname + "/data.json";
var xmlFullPath      = __dirname + "/data.xml";

//003 讀取JSON檔案的內容並解析為JavaScript物件(jsonData)
jsonfile.readFile(jsonFullPath,function(readJsonError,jsonData){
    if(!readJsonError)
    {
        //004 建立並返回bulider例項物件
        var bulider = new xml2js.Builder();
        //005 使用bulider物件將jsonData轉換為xml格式的字串
        var xml = bulider.buildObject(jsonData);
        //006 寫檔案操作(把最終的資料儲存到指定的檔案中)
        fs.writeFile(xmlFullPath, xml,  function(errorStatus) {
            if(! errorStatus)
            {
                console.log("json->XML 轉換成功");
                console.log(xml);
            }
        })
    }
})

列出用於轉換的初始json資料。

{
    "person": [
      {
        "age": 18,
        "name": "mitaoer",
        "hasBrother": true,
        "email": "[email protected]",
        "car": {
          "type": "英菲尼迪",
          "number": "京A 000001",
          "price": 200012.66
        }
      },
      {
        "age": 28,
        "name": "wendingding",
        "hasBrother": true,
        "email": "[email protected]",
        "car": {
          "type": "奧迪",
          "number": "京A 000002",
          "price": 200000.66
        }
      }
    ]
}

列出最終輸出的xml資料內容。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
  <age>18</age>
  <name>mitaoer</name>
  <hasBrother>true</hasBrother>
  <email>[email protected]</email>
  <car>
    <type>英菲尼迪</type>
    <number>京A 000001</number>
    <price>200012.66</price>
  </car>
  <age>28</age>
  <name>wendingding</name>
  <hasBrother>true</hasBrother>
  <email>[email protected]</email>
  <car>
    <type>奧迪</type>
    <number>京A 000002</number>
    <price>200000.66</price>
  </car>
</person>

如果JSON資料中存在陣列這種結構那麼使用xml2js模組的處理其實不甚理想,更新資訊請參考官方文件說明

MD,這篇文章寫了我好久中間一度放棄,玩了兩天農藥。好在,我終於完成了。