利用JsonSchema校驗json數據內容的合規性(轉)


原文地址:Json schema

背景:

復雜的AJAX應用程序可以與數百個不同的JSON服務進行交互,因此,引入對客戶端驗證的需求。

在處理校驗問題方面有着很多的工具,但是通常可以將它們歸為以下幾類:

    * 判斷數據是否已被正確格式化
    * 手動檢查形式有誤的數據並嘗試糾正
    * 手動檢查形式有誤的數據並將有誤數據丟棄
    * 自動檢查形式有誤的數據

在這里只討論自動校驗方面的可用工具包中的json schema,son schema項目首頁:http://json-schema.org/

JSON schema是一個幫助你定義、校驗甚至是修復json數據格式的解決方案。它定義了一整套規則,允許我們通過定義一個schema(本身也是JSON)來描述一個JSON串的數據格式。它有如下優點:

* 描述你現有的JSON數據的格式;
* 清晰的、人類/機器可讀的文檔結構;
* 完全的結構校驗,尤其適用於 自動測試 或 驗證客戶端提交的數據格式。

下面為一個定位信息的json schema例子

//json傳輸值
{
  "data" : {
    "id" : 851,
    "detail" : "琴千線長征路-萬盛南路附近",
    "area" : 9480,
    "province" : "浙江省",
    "parentArea" : 2819,
    "lng" : 120.32438,
    "district" : "東陽市",
    "lat" : 29.136176,
    "city" : "金華"
  }
}
//定位接口返回值的JSON schema
{
  "type" : "object",
  "properties" : {
    "data" : {
      "type" : "object",
      "properties" : {
        "id" : {
          "type" : "integer",
          "minimum": 0
        },
        "detail" : {
          "type" : "string"
        },
        "area" : {
          "type" : "integer"
        },
        "province" : {
          "type" : "string",
          "pattern" : "^(北京市|天津市|....|浙江省)$"
        },
        "parentArea" : {
          "type" : "integer"
        },
        "lng" : {
          "type" : "number",
          "minimum" : 73,
          "maximum" : 135
        },
        "district" : {
          "type" : "string"
        },
        "lat" : {
          "type" : "number",
          "minimum" : 4,
          "maximum" : 54
        },
        "city" : {
          "type" : "string"
        }
      },
      "required" : [
        "id",
        "detail",
        "area",
        "province",
        "parentArea",
        "lng",
        "district",
        "lat",
        "city"
      ]
    }
  },
  "required" : [
    "data"
  ]
}

可以看出:
1、json schema 本身也是一個json串

2、每個schema可以描述一個json實例,並且該json實例里每一個節點都可以用一個schema來描述,因此schema與json一樣,本身也是一個層級結構,一個schema中可能嵌套着另外若干層schema

3、json schema 定義的檢查規則以數據格式驗證為主(字段存在性、字段類型),並可以支持一些簡單的數據正確性驗證(例如數值范圍、字符串的模式等),但不能進行復雜的邏輯校驗(例如進價必須小於售價等)。

JS JSON Schema庫

表1中簡要概述了4個JSON Schema庫的特性

表 1. 針對 JavaScript 的 JSON Schema 驗證庫

庫(作者) 草案版本支持 庫的大概規模
JSV: JSON Schema 驗證器 (Gary Court) draft-01、draft-02 和 draft-03 120KB
json-schema (Kris Zyp) draft-03 10KB(需要 CommonJS)
dojox.json.schema (Kris Zyp) draft-02 10KB(需要 Dojo)
schema.js (Andreas Kalsch) draft-02(部分) 10KB(需要 CommonJS)

 

基於 Dojo 的應用程序可能會使用 dojox.json.schema 庫,因為該庫包含在工具箱中。支持多個版本的(草案)標准的應用程序可能會使用 JSV。

dojox.json.schema 看上去像是 json-schema 的一個分支,所以它們的用法非常相似。schema.js 只實現 draft-02 的一個子集。所以主要關注的是使用 dojox.json.schema 和 JSV 。

1. dojox.json.schema的使用

 

<html>
<head>
    <title>dojox.json.schema</title>
    <script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.0/dojo/dojo.js"
       type="text/javascript"></script>
    <script type="text/javascript">
    require(["dojox/json/schema"], function() {
        // Object to validate
        var successObj = { 
            "foo" : "bar"
        };
        var failureObj = {
            "foo" : 1234
        };

        // Schema
        var schema = { 
            "type": "object",
            "properties" : {
                "foo" : {
                    "type" : "string"
                }
            }
        };

        //var result = dojox.json.schema.validate(failureObj, schema);
        var result = dojox.json.schema.validate(successObj, schema);

        // Check results
        if (result.valid) {
            alert("Object is valid");
        } else {
            var errorArr = result.errors;
            alert("property : " + errorArr[0].property + "\nmessage :  " 
                + errorArr[0].message);
        }
    });
    </script>
</head>
<body>
Hello, World!
</body>
</html>

dojox.json.schema這種方法只需要引入一個js包,不過必須是線上的,將dojo那個js下載下來后就報錯不能執行。

2. JSV的使用

<head>
    <title>JSV</title>
    <!-- <script src="https://raw.github.com/garycourt/JSV/master/lib/uri/uri.js" type="text/javascript"></script>
    <script src="https://raw.github.com/garycourt/JSV/master/lib/jsv.js" type="text/javascript"></script>
    <script src="https://raw.github.com/garycourt/JSV/master/lib/json-schema-draft-03.js" type="text/javascript"></script> -->
    <script src="js/uri.js" type="text/javascript"></script>
    <script src="js/jsv.js" type="text/javascript"></script>
    <script src="js/json-schema-draft-03.js" type="text/javascript"></script>
    <script type="text/javascript">
        // Object to validate
        var successObj = { 
            "foo" : "bar"
        };
        var failureObj = {
            "foo" : 1234
        };

        // Schema
        var schema = { 
            "type": "object",
            "properties" : {
                "foo" : {
                    "type" : "string"
                }
            }
        };

        var env = JSV.createEnvironment("json-schema-draft-03"); 
        // validate
        var result = env.validate(successObj, schema);

        if (result.errors.length === 0) {
            alert("Object is valid");
        } else {
            var errorArr = result.errors;
            alert("uri : " + errorArr[0].uri + "\nmessage :  " 
                + errorArr[0].message);
        }
    </script>
</head>
<body>
Hello, World!
</body>
</html>

JSV這種方法,需要導入3個js包,這是必須下載后才能使用。
JSV 在 errors 數組中提供了一些高級故障信息。每個錯誤可能包含以下屬性:

* message:人類可讀的錯誤消息。


* uri:失敗對象所在位置的 URI。


* schemaUri:引起故障的模式的所在位置的 URI。


* Attribute:引起故障的模式約束。


* Details:包含更多詳細信息的自由格式數組,比如預期值。

使用對比:

dojox.json.schema只需要引入一個js包,基於 Dojo 的應用程序可能會使用 dojox.json.schema 庫,因為該庫包含在工具箱中。校驗的時候也只需要一行代碼即可:var result = dojox.json.schema.validate(successObj, schema); 其中successObj為傳入的JSON串,schema為校驗規則。

JSV需要引入三個js包,JSV支持draft-01,draft-02,draft-03三種草案,支持多個版本的(草案)標准的應用程序可能會使用 JSV。校驗的時候需要根據草案創建環境,然后再進行校驗。var env = JSV.createEnvironment(“json-schema-draft-03”);
var result = env.validate(successObj, schema); 其中successObj為傳入的JSON串,schema為校驗規則。JSV在errors數組中提供了一些高級的故障信息,包括message:可讀的錯誤信息;uri:失敗對象所在位置的URI;schemaUri:引起故障的模式所在位置的URI;Attribute:引起故障的模式約束;Details:包含更多詳細信息的自由格式數組,如果預期值。

性能對比:

一共執行50次,成功和失敗分開執行,每種情況執行25次。然后記錄下每次的執行時間,執行10次,取平均值。
dojox.json.schema:0.52, 4.28, 3.54, 4, 3.82, 3.64, 3.76, 4.12, 4.16, 5.6
JSV:4.5, 3.96, 3.88, 3.82, 3.98, 3.96, 3.9, 3.8, 4.1, 4.04

json schema類型 每次執行時間(ms)
dojox.json.schema 3.744
JSV 3.994

發現時間相差不多,JSV由於js包在本地,所以每次時間比較穩定;dojox.json.schema由於需要從網絡上去加載js包,導致執行時間有時會波動很大。整體來說,就執行過程,dojox.json.schema要快不少。

Java JSON Schema庫

表2給出了兩種java中使用的JSON Schema庫

庫名稱 地址 支持草案
fge https://github.com/daveclayton/json-schema-validator draft-04 draft-03
everit https://github.com/everit-org/json-schema draft-04

建議:

  • 如果在項目中使用了jackson json,那么使用fge是一個好的選擇,因為fge就是使用的jackson json。
  • 如果項目中使用的是org.json API,那么使用everit會更好。
  • 如果是使用以上兩個庫以外的庫,那么就使用everit,因為everit會比fge的性能好上兩倍。

fge的使用:

maven配置

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.3.0</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.3.0</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.3.0</version>
</dependency>

<dependency>
  <groupId>com.github.fge</groupId>
  <artifactId>json-schema-validator</artifactId>
  <version>2.2.6</version>
</dependency>

測試代碼:

@Test
public void testJsonSchema1() {
  JsonNode schema = readJsonFile("src/main/resources/Schema.json");
  JsonNode data = readJsonFile("src/main/resources/failure.json");
  ProcessingReport report = JsonSchemaFactory.byDefault().
                            getValidator().validateUnchecked(schema, data);
  Assert.assertTrue(report.isSuccess());
}

private JsonNode readJsonFile(String filePath) {
  JsonNode instance = null;
  try {
    instance = new JsonNodeReader().fromReader(new FileReader(filePath));
  } catch (IOException e) {
    e.printStackTrace();
  }
  return instance;
}

真正的調用只有一行代碼,需要傳入驗證規則和數據。分別有validate和validateUnchecked兩種方法,區別在於validateUnchecked方法不會拋出ProcessingException異常。

還可以從字符串中讀取json,代碼如下:

@Test
public void testJsonSchema2() {
  String failure = new String("{\"foo\":1234}");
  String Schema = "{\"type\": \"object\", \"properties\" : {\"foo\" : {\"type\" : \"string\"}}}";
  ProcessingReport report = null;
  try {
    JsonNode data = JsonLoader.fromString(failure);
    JsonNode schema = JsonLoader.fromString(Schema);
    report = JsonSchemaFactory.byDefault().getValidator().validateUnchecked(schema, data);
  } catch (IOException e) {
    e.printStackTrace();
  }
  //Assert.assertTrue(report.isSuccess());
  Iterator<ProcessingMessage> it = report.iterator();
  while (it.hasNext()) {
    System.out.println(it.next());
  }
}

其中ProcessingReport對象中維護了一共迭代器,如果執行失敗(執行成功時沒有信息),其提供了一些高級故障信息。每個錯誤可能包含以下屬性:

* level: 錯誤級別(應該就是error)
* schema:引起故障的模式的所在位置的 URI
* instance:錯誤對象
* domain:驗證域
* keyword:引起錯誤的約束key
* found:現在類型
* expected:期望類型

以上代碼的json信息為:

failure.json :   {"foo" : 1234}

Schema.json :
{
  "type": "object",
  "properties" : {
    "foo" : {
      "type" : "string"
    }
  }
}

執行錯誤信息為:

error: instance type (integer) does not match any allowed primitive type (allowed: ["string"])
    level: "error"
    schema: {"loadingURI":"#","pointer":"/properties/foo"}
    instance: {"pointer":"/foo"}
    domain: "validation"
    keyword: "type"
    found: "integer"
    expected: ["string"]

everit的使用:

maven配置(獲取最新版本)

<dependency>
  <groupId>org.everit.json</groupId>
  <artifactId>org.everit.json.schema</artifactId>
  <version>1.3.0</version>
</dependency>

測試代碼

@Test
public void testJsonSchema3() {
  InputStream inputStream = getClass().getResourceAsStream("/Schema.json");
  JSONObject Schema = new JSONObject(new JSONTokener(inputStream));
  JSONObject data = new JSONObject("{\"foo\" : 1234}");
  Schema schema = SchemaLoader.load(Schema);
  try {
    schema.validate(data);
  } catch (ValidationException e) {
    System.out.println(e.getAllMessage());
  }
}

如果驗證失敗會拋出一個ValidationException異常,然后在catch塊中打印出錯誤信息。everit中的錯誤信息想比fge來說比較簡單,相同的json測試文件,打印的信息如下:

#/foo: expected type: String, found: Integer

性能測試:

1、一共執行1000次,成功和失敗分開執行,每種情況執行250次。然后記錄下每次的執行時間,執行10次,取平均值。

fge每1000次的執行時間(ms):1158, 1122, 1120, 1042, 1180, 1254, 1198,1126,1177,1192
everit每1000次的執行時間(ms):33, 49, 54, 57, 51, 47, 48, 52, 53, 44

2、一共執行10000次,成功和失敗分開執行,每種情況執行2500次。

方法/場景 每次執行時間(ms)
fge/場景1 1.1569
fge/場景2 0.3407
everit/場景1 0.0488
everit/場景2 0.0206

使用對比:

從性能上來說everit完全是碾壓fge,官方說的至少兩倍,實際測試過程中,差不多有20倍的差距。雖然fge使用的是jackson json,相對來說學習成本可能較低,但是使用下來發現everit的使用也並不復雜,需要注意的是包需要導入正確(org.json)。fge唯一的優勢在於錯誤信息比較詳細。還有一點區別在於,everit驗證失敗是拋出異常,而fge是判斷返回一個boolean類型的值。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM