Bumpover.js - 牢固而趁手的數據校驗轉換庫

Bumpover 能幫助你編寫出簡潔明了的數據校驗與轉換代碼。通過熟悉的類型註解 API聲明式的轉換規則,你可以輕鬆地在運行期校驗未知的數據,並將其轉換為自己可控的格式。

穩定的數據結構對應用至關重要,但在持續的需求變更和版本迭代中,數據格式總是處於頻繁的變動之中。你當然可以編寫更多的 if else 邏輯來兼容不同類型的數據,不過這顯然會帶來更多枯燥、隨意而危險的麵條代碼。有沒有更好的方式呢?

現在,TypeScript 和 Flow 已經為我們帶來了非常方便的類型聲明 API,可以幫助你在編譯期檢測出潛在的類型問題。不過對於運行期未知的數據 - 如源自後端介面、文件系統和剪貼板粘貼的數據 - 它們的作用也相對有限:想想上次對接後端介面的時候你調了多久?

並且,在一般意義上的數據校驗之外,數據的轉換與遷移也是日常開發中非常常見的場景。除了數據可視化這樣需要頻繁轉換數據結構的場景外,對於一些將複雜 JSON 或 XML 內容序列化為字元串後存儲在關係型資料庫中的數據,它們在數據結構變動時,清洗起來是相當困難的:完成一道把 "<p>123</p>" 解析成 { paragraph: 123 } 的面試題是一回事,保證穩定可預期的數據轉換就是另一回事了。

Bumpover 就是設計來解決上面這幾個問題的。它通過結合來自 Superstruct 的類型聲明和規則驅動的數據更新機制,實現了:

  • 對 JSON 與 XML 格式數據聲明式的校驗 - 類似 JSON Schema,但輕便靈活得多。
  • 友好的類型註解 API,支持遞歸定義的數據類型。
  • 基於 Promise 的數據節點更新規則,可非同步轉換存在外部依賴的數據。
  • 靈活的數據遍歷機制,允許全量保留子節點、過濾未知節點等。
  • 可插拔的序列化和反序列化器,可輕鬆地支持各類私有數據格式。

說了這麼多,那麼 Bumpover 到底如何使用呢?耽誤你一分鐘的時間就夠了:

開始前,記得安裝依賴 :-)

npm install --save bumpover superstruct

將 Bumpover 與 Superstruct 導入到代碼庫中:

import { Bumpover } from "bumpover"import { struct } from "superstruct"

假設這個場景:你有一份數據,其內容可能是虛擬 DOM 樹中的節點,格式長這樣:

const maybeNode = { name: "div", props: { background: "red" }, children: []}

我們可以定義一個 struct 來校驗它:

import { struct } from "superstruct"const Node = struct({ name: "string", props: "object?", children: "array"})

現在我們就能用 Node 來校驗數據啦,將其作為函數調用即可:

Node(maybeNode)

一旦數據校驗失敗,你會獲得詳細的錯誤信息,而成功時會返回校驗後的數據。

現在如果我們需要轉換這份數據,該怎麼做呢?比如,如果我們需要把所有的 div 標籤換成 span標籤,並保留其它節點,該怎樣可靠地實現呢?你可以過程式地人肉遍曆數據,或者,簡單地定義規則

import { Bumpover } from "bumpover"const rules = [ { match: node => node.name === "div", update: node => new Promise((resolve, reject) => { resolve({ node: { ...node, name: "span" } }) }) }]const bumper = new Bumpover(rules)bumper.bump(data).then(console.log)// 獲得新節點數據

只要提供規則,bumpover 就會幫助你處理好剩下的臟活。注意下面幾點就夠了:

  • Rules 規則是實現轉換邏輯的 Single Source of Truth。
  • 使用 rule.match 匹配節點。
  • 使用 rule.update 在 Promise 內更新節點,這帶來了對非同步更新的支持:對一份富文本 XML 數據,在做數據遷移時可能需要將其中 <img> 標籤里的圖片鏈接重新上傳到雲端,成功後再將新的鏈接寫入新的數據結構中。Bumpover 能很好地支持這樣的非同步更新。
  • 將新節點包裝在 node 欄位內 resolve 即可。

這就是最基礎的示例了!對於更新後獲得的數據,你還可以為每條規則提供 rule.struct 欄位,校驗轉換得到的新節點是否符合你的預期。

轉換簡單的 JS 對象數據還不能完全體現出 Bumpover 的強大之處。考慮另一個場景:前端對 XML 格式數據的處理,一直缺乏易用的 API。除了原生 DOM 詭異的介面外,sax 這樣基於流的處理方式也十分沉重。而 Bumpover 則提供了開箱即用的 XMLBumpover 可以幫助你。同樣是把 <div> 轉換為 <span> 標籤,對 JSON 和 XML 格式數據的轉換規則完全一致

import { XMLBumpover } from "bumpover"const rules = [ { match: node => node.name === "div", update: node => new Promise((resolve, reject) => { resolve({ node: { ...node, name: "span" } }) }) }]const input = `<div> <div>demo</div></div>`const bumper = new XMLBumpover(rules)bumper.bump(input).then(console.log)// "<span><span>demo</span></span>"

這背後有什麼黑魔法呢?不存在的。對於你自己的各種神奇的數據格式,只要你能提供它與 JSON 互相轉換的 Parser,你就能編寫同樣的 Bumpover 規則來校驗並轉換它。作為例子,Bumpover 還提供了一個 JSONBumpover 類,能夠處理 JSON 字元串。我們來看看它的實現源碼:

import { Bumpover } from "./index"export class JSONBumpover extends Bumpover { constructor (rules, options) { super(rules, options) this.options = { ...this.options, serializer: JSON.stringify, deserializer: JSON.parse } }}

只要提供了 JSON.parseJSON.stringify,你就能支持一種全新的數據類型了。並且,你還可以把 xml2jsJSON.stringify 相結合,定製出更靈活的數據轉換器 ??

如果這些實例讓你有了點興趣,Bumpover 項目下還有一份完整的 Walkthrough,介紹如何使用 Bumpover 實現非同步遷移、及早返回、過濾節點等更靈活的特性,輔以完整的 API 文檔。並且,Bumpover 雖然才開始開發不到一周,但已經實現了測試用例的 100% 代碼覆蓋率,歡迎感興趣的同學前來體驗哦 ??

最後作為一點花絮,介紹一下筆者開發 Bumpover 的動機,以及它和 Superstruct 的淵源:Superstruct 與 Slate 富文本編輯框架師出同門,而筆者本人恰好是這個編輯器的主要貢獻者之一。Slate 在 v0.30 左右遇到了編輯器 Schema 校驗的各種問題,而 Superstruct 就是一個應運而生,允許自定義更靈活 Schema 的新輪子。而筆者在實際使用中發現 Superstruct 還能夠推廣到更一般的場景下,這就是 Bumpover 誕生的源動力了。

Bumpover 還處於非常早期的階段,非常希望各位 dalao 們能夠賞臉支持~謝謝!

  • Bumpover Repo
  • Superstruct Repo
  • Slate 介紹

推薦閱讀:

JSON适合大数据传输吗?跨语言JSON数据传输需要注意什么?
大家一般用什麼工具測試HTTP,json介面?

TAG:JavaScript | 前端开发 | JSON |