1. 程式人生 > >服務端json引數校驗神器Json Schema

服務端json引數校驗神器Json Schema

目錄

json簡介

JSON(JavaScript Object Notation) 是一種輕量級的資料交換格式。 易於人閱讀和編寫。同時也易於機器解析和生成。 它基於JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一個子集。 JSON採用完全獨立於語言的文字格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 這些特性使JSON成為理想的資料交換語言。 舉個栗子:

{ "an": [ "arbitrarily"
, "nested" ]
, "data": "structure" }

服務端校驗引數需求分析

引數檢測是伺服器端拿到引數的第一步操作,後續的一系列操作都完全信任資料的可靠安全性。可以準確的說,前端的引數校驗是為了適當的保護服務端,為了更好的使用者體驗,但服務端萬萬不可信任前端傳遞的引數,所有的引數一定要進行校驗,確保輸入資料是合法正確性,這是服務端程式設計師的基本準則。常見的引數檢驗包括非空,最大長度檢測,大小檢測等等。應用在執行業務邏輯之前,必須通過校驗保證接受到的輸入資料是合法正確的,但很多時候同樣的校驗出現了多次,在不同的層,不同的方法上,導致程式碼冗餘,浪費時間。

json引數檢驗簡單而繁瑣方式

可以針對每個欄位,進行一系列的引數校驗,然後完成該json引數的檢驗。 舉個栗子:

dict = { "an": [ "arbitrarily", "nested" ], "data": "structure" }
if dict.get(data, '')=='':
    return 'key data is no null'
if len(dict.get(data, ''))>30:
    return "data is too long"   
other verify

就是這樣不斷的針對每一個欄位進行校驗,確保輸入的資料的可靠、安全性。 缺點:很多時候同樣的校驗出現了多次,在不同的層,不同的方法上,導致程式碼冗餘,浪費時間。比如校驗是否為空,檢驗長度對大等等大量重複程式碼,效率很低。

Json Schema

相信用過flask-wtf的同學都會很喜歡這樣的校驗工具,自動對每個欄位進行,而且效率還很高,不用寫大量的重複程式碼,開發效率會得到大大的提升。反正我在用html的表單提交時都會使用flask-wtf,只能說,你用過一次就會愛上它。 那麼當前端提交的資料不是form而是json時怎麼辦呢?如果不想像上面一樣每個欄位或者每個資料都一一檢測,這時候你可以嘗試使用json schema,很像flask-wtf的一個開源框架,專門用來校驗json或者可以轉換成json的dict。

Json Schema 入門

先舉個栗子,大致說一下json schema怎麼用

>>> from jsonschema import validate

>>> # A sample schema, like what we'd get from json.load()
>>> schema = {
...     "type" : "object",
...     "properties" : {
...         "price" : {"type" : "number"},
...         "name" : {"type" : "string"},
...     },
... }

>>> # If no exception is raised by validate(), the instance is valid.
>>> validate({"name" : "Eggs", "price" : 34.99}, schema)

>>> validate(
...     {"name" : "Eggs", "price" : "Invalid"}, schema
... )                                   # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
    ...i
ValidationError: 'Invalid' is not of type 'number'

你可以使用validate來驗證輸入的json是否符合你需要的模式,如果不符合要求會丟擲ValidationError異常,ValidationError.message 會提示不符合哪個要求或哪個要求出錯,ValidationError.instance會提示是不符合該要求的欄位的內容或者例項是什麼。具體的schema 的表示式該怎麼寫後面會介紹到。

Json Schema 表示式

json schema的表示式在這裡就講點基本的好了,其實基本的已經夠用了,如果要想更深入學習,可以到官網json schema進行學習。 json schema能支援的型別有string,Numeric types,object,array,boolean,null。接下來會一一解釋。

string

舉個例子來說明:

{
  "type": "string",
  "minLength": 2,
  "maxLength": 3
}
'A' is wrong,  'aa' is ok, 'aaa' is ok ,'aaaasf' is wrong

這表明它需要一個string欄位,其中string 有的屬性有:

  • minLength
  • maxLength
  • pattern (正則表示式驗證)
  • format (目前支援:”date-time”,”email”,”hostname”,”ipv4”,”ipv6”,”uri”, 如果沒有你需要的格式,你可以用pattern 的正則表示式來完成)

Numeric types

老慣例,上來舉個例子:

{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "exclusiveMaximum": true
}
ok:
0
10
99
wrong:
-1
100
101

這表明它需要一個Numeric types欄位,其中number有的屬性有:

  • type(integer,number)
  • multipleOf(要求該數字是這個數的整數倍)
  • minimum 、exclusiveMinimum 、maximum 、exclusiveMaximum (最大值、最小值以及是否包含該最大最小值)

object

在Python中,object類似於dict型別,”type”:”object”就是宣告需要dict類。舉個例子,就能大致理解了:

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

這表明它需要dict的型別。object具有的屬性有:

  • additionalProperties: 是否可以需要多其它的字典關鍵字。例如”additionalProperties”: { “type”: “string” }表明允許多的關鍵字必須是string型別,additionalProperties:false則表明不允許多其它的關鍵字。
  • required:表明必須要有該關鍵字,例如 “required”: [“number”, “street_name”] 必須要有這number和street_name這兩個key。
  • minProperties,maxProperties 表明這個字典最少、最多擁有多少個keys。
  • dependencies:依賴屬性,例如 “dependencies”: { “credit_card”:[“billing_address”]}表明有credit_card是必須有billing_address這個key。
  • patternProperties:key為正則表示式。例如”patternProperties”: {“^S_”: { “type”: “string” }},需要S_開頭key

    array

    array對應python裡的[],可以是列表也可以是原組,舉個例子:
{
  "type": "array",
  "items": [
    {
      "type": "number"
    },
    {
      "type": "string"
    },
    {
      "type": "string",
      "enum": ["Street", "Avenue", "Boulevard"]
    },
    {
      "type": "string",
      "enum": ["NW", "NE", "SW", "SE"]
    }
  ]
}
則
[1600, "Pennsylvania", "Avenue", "NW"] is ok

“Drive” is not one of the acceptable street types:
[24, "Sussex", "Drive"] is wrong

This address is missing a street number 
["Palais de l'Élysée"] is wrong

It’s okay to not provide all of the items:
[10, "Downing", "Street"] is ok

And, by default, it’s also okay to add additional items to end:
[1600, "Pennsylvania", "Avenue", "NW", "Washington"] is ok

item具有的屬性是: - additionalItems :該additionalItems關鍵字控制是否有效有超出了所定義的陣列中的其他專案items。在這裡,我們將重用上面的示例模式,但設定 additionalItems為false,這會導致不允許陣列中的額外項。 - minItems、maxItems:minItems和 maxItems關鍵字指定陣列的長度。每個關鍵字的值必須是非負數。 - Uniqueness:模式可以確保陣列中的每個項都是唯一的。只需將uniqueItems關鍵字設定為true。

boolean

在Python中,“boolean”類似於bool。請注意,在JSON中, true它false是小寫的,而在Python中它們是大寫的(True和False)。 舉個例子:
{ "type": "boolean" }
OK:
true
false
wrong:
"true"
0

布林型別只匹配兩個特殊值:true和 false。請注意,架構不接受評估為true或的值false,例如1和0

null

在Python中,null類似於None。
{ "type": "null" }
ok:
null
wrong:
false
0
""

null型別通常用於表示缺失值。當模式指定a type時null,它只有一個可接受的值:null

實戰演習

USERS_SCHEMA = {
    "type": "object",
    "required": ["users"],
    "properties": {
        "users": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["name", "email"],
                "properties": {
                    "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 30,
                    },
                    "primary_sector": {
                        "type": "string",
                        "maxLength": 60,
                    },
                    "second_sector": {
                        "type": "string",
                        "maxLength": 60,
                    },
                    "tertiary_sector": {
                        "type": "string",
                        "maxLength": 60,
                    },
                    "email": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 60,
                        "pattern":  '^([a-zA-Z0-9]+[-_.]?)*[a-zA-Z0-9][email protected]([a-zA-Z0-9]+[-_.]?)*'\
                                    '[a-zA-Z0-9]+\.[a-zA-Z]{2,6}$',
                    }
                },
            },
        },
    },
}

用來對應的符合要求的json是

{
    "users":{
        [{
            "name": "hello",
            "primary_sector": "一級部門",
            "second_sector": "二級部門",
            "tertiary_sector": "三級部門",
            "email" : "郵箱",
        }],
        [{
            "name": "hello1",
            "primary_sector": "一級部門",
            "second_sector": "二級部門",
            "tertiary_sector": "三級部門",
            "email" : "郵箱",
        }],
    }
}

可以一次性驗證json裡含有多有輸入使用者資料的json。

總結

Json Schema給人感覺比較像在寫json正則表示式,按著基本的幫助文件和知道,通過組合基本就能寫出你需要的json驗證格式,然後驗證的時候只需要一句validate()就可以了,如果驗證失敗會丟擲異常。建議以後驗證json格式還是習慣使用這種驗證方式,會大大提高開發效率。

原文地址

參考文獻