JSON Schema 介紹及應用

  • 2019 年 12 月 4 日
  • 筆記

本文作者:IMWeb jerytang 原文出處:IMWeb社區 未經同意,禁止轉載

一、如何描述 JSON ?

JSON (JavaScript Object Notation) 縮寫,JSON 是一種數據格式,具有簡潔、可讀性高、支援廣泛的特點。JSON 有以下基本數據類型

// # 1. object  { "key1": "value1", "key2": "value2" }    // # 2. array  [ "first", "second", "third" ]    // # 3. number  42    // # 4. string    "This is a string"    // # 5. boolean    true    false    // # 6. null    null

在其它語言中也有類似的內建數據類型,但是由於 JavaScript的廣泛應用,而 JSON 作 為 JavaScript原生的數據類型,具備更加廣泛的支援。

有了上面列舉的基本數據類型,JSON 能非常靈活的表示任意複雜的數據結構。舉個例子:

  {        "name": "George Washington",        "birthday": "February 22, 1732",        "address": "Mount Vernon, Virginia, United States"    }

如何描述上面 JSON 對象呢?

首先,它是一個 object

其次,它擁有 namebirthdayaddress 這三個欄位

並且,nameaddress 的欄位值是一個字元串 Stringbirthday 的值是一個日期。

最後,將上面的資訊如何用 JSON 來表示?如下:

  {        "type": "object",        "properties": {             "name": { "type": "string" },             "birthday": { "type": "string", "format": "date" },             "address": { "type": "string" }        }    }

這個表示就是一個 JSON Schema ,JSON Schema 用於描述 JSON 數據。

相同的數據,可能有不同的表示,比如下面的兩種表示,包含的資訊量基本是一致的:

  // # 1. 表示一    {    "name": "George Washington",    "birthday": "February 22, 1732",    "address": "Mount Vernon, Virginia, United States"    }      // # 2. 表示二    {        "first_name": "George",        "last_name": "Washington",        "birthday": "1732-02-22",        "address": {            "street_address": "3200 Mount Vernon Memorial Highway",            "city": "Mount Vernon",            "state": "Virginia",            "country": "United States"        }    }

在特定的應用場景中,應用程式對數據的結構要求是確定的,出於對數據描述的規範化需 求,需要用 JSON schema 來規範化。使用 JSON schema 可以描述 JSON 數據所包含的字 段、以及欄位值的類型,以及依賴關係等。

相同資訊量的數據,採用不同的形式來表達,用 JSON schema 來描述也是不一樣的,表示二的 JSON Schema 如下:

  {        "type": "object",        "properties": {             "first_name": { "type": "string" },             "last_name": { "type": "string" },             "birthday": { "type": "string", "format": "date-time" },             "address": {             "type": "object",             "properties": {                 "street_address": { "type": "string" },                 "city": { "type": "string" },                 "state": { "type": "string" },                 "country": { "type" : "string" }             }            }        }    }

從上面的描述,可以很自然的想到 JSON Schema 可以用來做數據校驗,比如前後端先把數 據介面約定好,寫好 JSON Schema,等後端把介面輸出完畢,直接用 JSON Schema 來對接 口做驗收。

關於 JSON Schema 的應用,對 JSON Schema 有過了解的人可以直接跳到第三、四部分。

接下來對 JSON Schema 做一些舉例說明。

二、JSON Schema 的舉例

1. 空 schema

{}

以下都是合法的 JSON

42    "I'm a string"    [{"an": "aaa","bbb":{"nest":"data"}}]

2. type 指定 JSON 數據類型

{ "type": "string" }
"I'm a string"    42    { "type": "number" }    42    "42"

type 的可能取值: stringnumberobjectarraybooleannull

3. type 可以包含多個類型

{ "type": ["number", "string"] }
"I'm a string" // 合法    42  // 合法    ["Life", "the universe", "and everything"] // 不合法

4. string 限定長度

{      "type": "string",      "minLength": 2,      "maxLength": 3  }
"AA" // 合法  "AAA" // 合法  "A"  // 不合法  "AAAA" // 不合法

5. string 模式匹配

{    "type": "string",    "pattern": "^(\([0-9]{3}\))?[0-9]{3}-[0-9]{4}$"  }
"555-1212" // ok    "(888)555-1212" // ok    "(888)555-1212 ext. 532" // not ok    "(800)FLOWERS" // not ok

6. string 值的枚舉

{      "type": "string",      "enum": ["red", "amber", "green"]  }
"red" // ok    "blue" // not ok: blue 沒有在 enum 枚舉項中

7. integer

integer 一定是整數類型的 number

{ "type": "integer" }
42 // ok  1024 // ok

8. multipleOf 數字倍數

{ "type": "number", "multipleOf": 2.0 }
42 // ok  21 // not ok

9. number 限定範圍

{      "type": "number",      "minimum": 0,      "maximum": 100,      "exclusiveMaximum": true  }

exclusiveMaximumtrue 表示包含邊界值 maximum,類似的還有 exclusiveMinimum 欄位.

10. object 不允許有額外的欄位

{      "type": "object",      "properties": {          "number": { "type": "number" },          "street_name": { "type": "string" },          "street_type": {               "type": "string",               "enum": ["Street", "Avenue", "Boulevard"]          }      },      "additionalProperties": false  }
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" } // ok  { "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue","direction": "NW" } // not ok

因為包含了額外的欄位 direction,而 schema 規定了不允許額外的欄位 "additionalProperties": false

11. object 允許有額外的欄位,並限定類型

{      "type": "object",      "properties": {      "number": { "type": "number" },      "street_name": { "type": "string" },      "street_type": {          "type": "string",          "enum": ["Street", "Avenue", "Boulevard"]      }      },      "additionalProperties": { "type": "string" }  }
{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue","direction": "NW" } // ok    { "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "office_number": 201 } // not ok
  額外欄位 `"office_number": 201` 是 number 類型,不符合 schema

12. object 必填欄位

{       "type": "object",       "properties": {            "name": { "type": "string" },            "email": { "type": "string" },            "address": { "type": "string" },            "telephone": { "type": "string" }       },       "required": ["name", "email"]  }
// ok  {    "name": "William Shakespeare",    "email": "[email protected]"  }

多出欄位也是 ok 的

// ok  {      "name": "William Shakespeare",      "email": "[email protected]",      "address": "Henley Street, Stratford-upon-Avon, Warwickshire, England",      "authorship": "in question"  }

少了欄位,就是不行

// not ok  {      "name": "William Shakespeare",      "address": "Henley Street, Stratford-upon-Avon, Warwickshire, England",  }

13. object 指定屬性個數

{      "type": "object",      "minProperties": 2,      "maxProperties": 3  }
{ "a": 0, "b": 1 } // ok  { "a": 0, "b": 1, "c": 2, "d": 3 } // not ok

14. Dependencies 依賴

  略複雜,不提供示例

15. Object 屬性的模式匹配

{      "type": "object",      "patternProperties": {           "^S_": { "type": "string" },           "^I_": { "type": "integer" }      },      "additionalProperties": false  }
{ "S_25": "This is a string" } // ok    { "I_0": 42 } // ok
// not ok  { "I_42": "This is a string" }    { "keyword": "value" }

16. array 數組

// ok  { "type": "array" }  [1, 2, 3, 4, 5]  [3, "different", { "types" : "of values" }]
// not ok:  {"Not": "an array"}

17. array 指定數組成員類型

{      "type": "array",      "items": {          "type": "number"      }  }
[1, 2, 3, 4, 5] // ok  [1, 2, "3", 4, 5] // not ok

18. array 指定數組成員類型,逐個指定

{  "type": "array",       "items": [{            "type": "number"            },{            "type": "string"            },{            "type": "string",            "enum": ["Street", "Avenue", "Boulevard"]            },{            "type": "string",            "enum": ["NW", "NE", "SW", "SE"]       }]  }
// ok  [1600, "Pennsylvania", "Avenue", "NW"]    [10, "Downing", "Street"] // 缺失一個也是可以的    [1600, "Pennsylvania", "Avenue", "NW", "Washington"] // 多出一個也是可以的
// not ok  [24, "Sussex", "Drive"]  ["Palais de l'Élysée"]

19. array 指定數組成員類型,逐個指定,嚴格限定

{      "type": "array",      "items": [{          "type": "number"          },          {          "type": "string"          },          {          "type": "string",          "enum": ["Street", "Avenue", "Boulevard"]          },          {          "type": "string",          "enum": ["NW", "NE", "SW", "SE"]          }      ],      "additionalItems": false  }
[1600, "Pennsylvania", "Avenue", "NW"] // ok    [1600, "Pennsylvania", "Avenue"] // ok    [1600, "Pennsylvania", "Avenue", "NW", "Washington"] // not ok 多出了欄位就是不行

20. array 數組長度限制

{     "type": "array",     "minItems": 2,     "maxItems": 3  }
[1, 2] // ok    [1, 2, 3, 4] // not ok

21. array element uniqueness 數組元素的唯一性

{      "type": "array",      "uniqueItems": true  }
[1, 2, 3, 4, 5] // ok  [1, 2, 3, 3, 4] // not ok:出現了重複的元素 3

22. boolean

{ "type": "boolean" }
true // ok  0 // not ok

23. null

{ "type": "null" }
null // ok    "" // not ok

24. schema 的合併

string 類型,最大長度為 5 ;或 number 類型,最小值為 0

{      "anyOf": [         { "type": "string", "maxLength": 5 },         { "type": "number", "minimum": 0 }      ]  }    `anyOf` 包含了兩條規則,符合任意一條即可
"short"  // ok  42 // ok  "too long" // not ok 長度超過 5  -5 // not ok 小於了 0

25. allOf、oneOf

  `anyOf` 是滿足任意一個 Schema 即可,而 `allOf` 是要滿足所有 Schema    `oneOf` 是滿足且只滿足一個

26. oneOf

{      "oneOf": [          { "type": "number", "multipleOf": 5 },          { "type": "number", "multipleOf": 3 }      ]  }
10 // ok  15 // not ok 因為它既是 3 又是 5 的倍數

上面的 schema 也可以寫為:

{      "type": "number",      "oneOf": [          { "multipleOf": 5 },          { "multipleOf": 3 }      ]  }

27. not

{ "not": { "type": "string" } }

只要是非 string 類型即可

42 // ok  {"key" : "value"} // ok  "This is a string" // not ok

三、JSON schema 的應用一:對數據做驗證

驗證庫 jsonschema

   var Validator = require('jsonschema').Validator;     var v = new Validator();     var instance = 4;     var schema = {"type": "number"};     console.log(v.validate(instance, schema));

介面數據校驗

在實際開發中,前端和後端會約定介面,前端根據約定的介面,使用 mock 的數據來開發 demo,而後端去實現介面,前端和後端可以同步進行。等後端開發完畢後,可以通過預先 寫好的腳本對返回介面進行批量的數據校驗。

四、JSON schema 的應用二:根據 JSON Schema 生成數據採集 UI

對數據進行校驗是在數據輸出端保證數據的正確性,有沒有什麼方式能在數據輸出時就保證數據正確性呢?

這裡提供一種思路,觀察 schema 可以發現,其實一條 Schema 描述已經包含了相當豐富 的數據資訊了,每一種 schema 類型其實可以對應了一種 UI 展示,那麼一條 Schema 其 實是可以生成一個表單,表單的 UI 邏輯中保證在提交表單前,數據是符合 Schema 規則 的,表單驗證通過後,得到的就是符合 Schema 的 JSON 數據。

通過這個思想,可以做一套 JSON Schema -> UI -> JSON 的運營數據採集系統,見文章 開始的配圖。效果如下圖:

具體的實現,我們留到以後再談。

參考