12/08/2018, 14:35

Validate Json data (Phần cuối)

Ở những phần trước, mình đã giới thiệu cách dùng cơ bản với ajv , các từ khóa quan trọng, ý nghĩa và cách dùng của mỗi từ khóa theo mỗi kiểu dữ liệu khác nhau. Bạn có thể xem lại Phần 1 và Phần 2 để xem cách sử dụng cơ bản. Với bài viết này, mình sẽ tổng hợp lại một chút những thứ đã được đề cập ở ...

Ở những phần trước, mình đã giới thiệu cách dùng cơ bản với ajv, các từ khóa quan trọng, ý nghĩa và cách dùng của mỗi từ khóa theo mỗi kiểu dữ liệu khác nhau. Bạn có thể xem lại Phần 1 và Phần 2 để xem cách sử dụng cơ bản. Với bài viết này, mình sẽ tổng hợp lại một chút những thứ đã được đề cập ở những phần trước, đồng thời đưa ra những đề cập mới đến do hạn chế về kiến thức chưa được đề cập sâu ở những phần trước.

Cấu trúc Schema

  • Json-schema chuẩn cho phép bạn tách các chema ra thành nhiều phần nhỏ. Ví dụ
    {
      "level": 1,
      "parent_id": null,
      "visitors": "all",
      "color": "white",
      "pages": [
        {
          "page_id": 1,
          "short_name": "home",
          "display_name": "Home",
          "url": "/home",
          "navigation": {
            "level": 2,
            "parent_id": 1,
            "color": "blue",
            "pages": [
              {
                "page_id": 11,
                "short_name": "headlines",
                "display_name": "Latest headlines",
                "url": "/home/latest",
                "navigation": {
                  "level": 3,
                  "parent_id": 11,
                  "color": "white",
                  "pages": [
                    {
                      "page_id": 111,
                      "short_name": "latest_all",
                      "display_name": "All",
                      "url": "/home/latest"
                    },
                    ...
                  ]
                }
              },
              {
                "page_id": 12,
                "short_name": "events",
                "display_name": "Events",
                "url": "/home/events"
              }
            ]
          }
        },
        ...
      ]
    

Cấu trúc dữ liệu nhìn khá phức tạp và bị đệ quy, nhưng chúng ta có thể miêu tả dữ liệu bằng một cấu trúc schema đơn giản hơn một chút như sau:

navigation.json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id": "http://mynet.com/schemas/navigation.json#",
  "title": "Navigation",
  "definitions": {
    "positiveIntOrNull": { "type": ["null", "integer"], "minimum": 1 }
  },
  "type": "object",
  "additionalProperties": false,
  "required": [ "level", "parent_id", "color", "pages" ],
  "properties": {
    "level":     { "$ref": "defs.json#/definitions/positiveInteger" },
    "parent_id": { "$ref": "#/definitions/positiveIntOrNull" },
    "visitors":  { "enum": [ "all", "subscribers", "age18" ] },
    "color":     { "$ref": "defs.json#/definitions/color" },
    "pages":     {
      "type": "array",
      "items": { "$ref": "page.json#" }
    }
  }
}
    page.json:

    {
      "$schema": "http://json-schema.org/draft-04/schema#",
      "id": "http://mynet.com/schemas/page.json#",
      "title": "Page",
      "type": "object",
      "additionalProperties": false,
      "required": [ "page_id", "short_name", "display_name", "path" ],
      "properties": {
        "page_id":      { "$ref": "defs.json#/definitions/positiveInteger" },
        "short_name":   { "type": "string", "pattern": "^[a-z_]+$" },
        "display_name": { "type": "string", "minLength": 1 },
        "path":         { "type": "string", "pattern": "^(?:/[a-z_-]+)+$" },
        "color":        { "$ref": "defs.json#/definitions/color" },
        "navigation":   { "$ref": "navigation.json#" }
      }
    }

   defs.json:
   {
     "$schema": "http://json-schema.org/draft-04/schema#",
     "id": "http://mynet.com/schemas/defs.json#",
     "title": "Definitions",  
     "definitions": {
       "positiveInteger": { "type": "integer", "minimum": 1 },
       "color": {
         "anyOf": [
           { "enum": [ "red", "green", "blue", "white" ] },
           { "type": "string", "pattern": "^#(?:(?:[0-9a-fA-F]{1,2})){3}$" }
         ]
       }
     }
   }

Như đã trình bày ở trên, thì việc tách Schema ra thành những phần schema khác nhau giúp việc cấu trúc dữ liệu có vẻ dễ hiểu hơn, mà hiểu nôm na là sẽ đẹp hơn. Tuy nhiên, ở đây có một sự thay đổi nhất định khi mà navigation.json truy cập đến page.json, vì vậy việc sử dụng schema để validate data có thay đổi như sau:

        var Ajv = require('ajv');
        var ajv = Ajv({
          allErrors: true,
          schemas: [
            require('./navigation.json'),
            require('./page.json'),
            require('./defs.json')
          ]
        });
        var validate = ajv.getSchema("http://mynet.com/schemas/navigation.json#");
        var valid = validate(navigationData);
        if (!valid) console.log(validate.errors);

Ajv được đánh giá là Json-Schema validator cho JavaScript nhanh nhất. Ajv các bác nhá, không phải jav đâu.

Truy cập giữa hai Schema sử dụng từ khóa "$$ef"

  • Như đã tra đổi ở trên, $$ef cũng là một từ khóa của ajv tuy nhiên để hiểu cặn kẽ và sử dụng chính xác nó thì không hề đơn giản. Nó chưa phù hợp với nội dung ở Phần 1 và Phần 2 của phần giới thiệu sử dụng ajv để validate json-data. Những phần trước mang tính giới thiệu cơ bản cách sử dụng và những thứ nguyên thủy, dễ hiểu, để làm nền cho những kiến thức tầm cao hơn sẽ được trình bày ở bài viết này.
  • Một Json-schema chuẩn sẽ cho phép tái sử dụng và lặp lại một phần của các schema sử dụng từ khóa $$ef. Giống như bạn nhìn trên ví dụ trên, bạn có thể truy cập tới các schema và tổ chức chúng giống như là:
    • Ở một file, sử dụng Schema URI để định nghĩa thuộc tính id của nó.
    • Ở một phần khác của file, dựa vào Json pointer để truy cập đến Schema
    • Trong một phần của schema hiện tại thì thêm vào # sau json-pointer
  • Bạn có thể sử dụng $$ef tương ứng với # để tạo ra những schema đệ quy.
  • Trong ví dụ của chúng ta navigation.json đề cập đến:
    • schema ở page.json
    • definition của defs.json
    • phần định nghĩa positiveIntOrNull trong chính navigation.json
  • page.json đề cập đến :
    • navigation.json
    • definitions trong defs.json

Ở mức chuẩn $$ef chỉ được sử dụng trong thuộc tính của một object, vì vậy bạn muốn áp dụng nó để truy cập đến schema như là một thêm vào của một schema khác, bạn phải sử dụng từ khóa allOf

Schema Ids

  • Schema thường có một thuộc tính đầu tiên là id, nó được gọi là schema URI. Khi $$ef được sử dụng ở một schema nào đó, thì giá trị của nó sẽ được luận giải như một URI truy cập đến schema id tương ứng.

  • Giải pháp này hoạt động giống như là browser URI. Nếu $$ef là một tên file, nó có thể thay thế tên file bằng id. Trong ví dụ trên, trong navigation.json, id của nó là http://mynet.com/schemas/navigation.json#, vì vậy , khi truy cập đến page.json , URI đầy đủ của page schema sẽ trở thành http://mynet.com/page.json#, và "/folder/page.json" sẽ có thể biết đến giống như là http://mynet.com/folder/page.json#

  • Nếu $$ef bắt đầu bằng #, nó được luận giải là 1 phần được thêm vào của id. Trong navigation , sự truy cập defs.json#/definitions/color có thể hiểu là URI là http://mynet.com/schemas/defs.json#/definitions/color , khi đó http://mynet.com/schemas/defs.json# được hiểu là *id, còn definition/color được hiểu là Json-pointer trong đó.

  • Nếu $$ef là một URI đầy đủ khác nhau ở domain name, ( cái gì là domain name?) nó cũng hoạt động giống như cách các link hoạt động trên browser.

Internal Schema IDs

  • Json-Schema chuẩn cho phép bạn sử dụng id trong chính nó để nhận dạng những schema con và nó sẽ thay đổi dựa trên URI cơ bản mà nó tương quan với schema cha. Nó được gọi là "changing resolution scope". Vấn đề là nó có thể gây ra những sự nhầm lẫn đáng kể theo chuẩn, do đó, nó cũng không được sử dụng một cách phổ biến.

  • Tôi không khuyến khích việc sử dụng internal IDstrong trường hợp hội tụ đầy đủ 2 lý do sau:

    • Có rất ít các validator tuân theo chuẩn và có giải pháp đúng đắn để truy cập khi internal Ids được sử dụng.
    • Schemas trở lên khó hiểu hơn.
  • Chúng ta vẫn phải nhìn xem cách nó hoạt động như thế nào bởi vì bạn có thể gặp gỡ những schemas sử dụng "internal Ids" và trong những trường hợp đó, khi sử dụng chúng có thể giúp chúng ta cấu trúc lại schemas.

  • Đầu tiên, chúng ta hãy nhìn vào ví dụ navigation. Mọi truy cập đến definitions đề khá là dài. Ở đây có một con đường ngắn hơn là thêm IDs vào definitions. Schema sẽ trở thành như sau:

    navigation
    
    {
      "$schema": "http://json-schema.org/draft-04/schema#",
      "id": "http://mynet.com/schemas/defs.json#",
      "title": "Definitions",  
      "definitions": {
        "positiveInteger": { "id": "#positiveInteger", "type": "integer", "minimum": 1 },
        "color": {
          "id": "#color",
          "anyOf": [
            { "enum": [ "red", "green", "blue", "white" ] },
            { "type": "string", "pattern": "^#(?:(?:[0-9a-fA-F]{1,2})){3}$" }
          ]
        }
      }
    }
    
    

Giờ đây , thay vì việc truy cập qua "defs.json#/definitions/positiveInteger" và "defs.json#/definitions/color", bạn có thể sử dụng đường link ngắn hơn là ""defs.json#positiveInteger" và "defs.json#color". Nó là cách sử dụng phổ biến của internal Ids là nó làm cho việc truy cập đến các thành phần một cách ngắn hơn và dễ đọc hơn. Chú ý rằng, trong ví dụ đơngiản này có thể đúng với hầu hết Json-Schema validator, nhưng cũng có một số validator không hỗ trợ.

  • Chúng ta hãy đến với một ví dụ phức tạp hơn.

    {
      "id": "http://x.y.z/rootschema.json#",
      "definitions": {
        "bar": { "id": "#bar", "type": "string" }
      },
      "subschema": {
        "id": "http://somewhere.else/completely.json#",
        "definitions": {
          "bar": { "id": "#bar", "type": "integer" }
        },
        "type": "object",
        "properties": {
          "foo": { "$ref": "#bar" }
        }
      },
      "type": "object",
      "properties": {
        "bar": { "$ref": "#/subschema" },
        "baz": { "$ref": "#/subschema/properties/foo" },
        "bax": { "$ref": "http://somewhere.else/completely.json#bar" }
      }
    }
    
  • Có một số dòng rất dễ gây nhầm lẫn. trong ví dụ, khá là khó để biết thuộc tính nào là string, integer

  • Schema định nghĩa một object có các thuộc tính bar, bax, baz. Thuộc tính bar là một object được định ghĩa trong schema con, và trong schema con, thuộc tính foo của nó lại truy cập đến bar. Bởi vì schema con có thuộc tính id của nó, nên URI để truy cập đến là : "http://somewhere.else/completely.json#bar", và nó là integer.

  • Suy luận tương tự, ta sẽ có phân biệt các giá trị khả dụng của các thuộc tính là :

    {
      "bar": { "foo": 1 },
      "baz": 2,
      "bax": 3
    }
    

Cảm ơn các bạn đã đọc. Một bật mí nho nhỏ cho các bạn khi sử dụng đến validate Json, bạn có thể truy cập vào đây để lấy luôn schema nếu dữ liệu đầu vào là đơn giản

0