Bumpover.js – 牢固而趁手的資料校驗轉換庫

doodlewind發表於2019-02-28

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 格式資料的轉換規則完全一致

XML 轉換需要安裝 xml-js 依賴

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 們能夠賞臉支援~謝謝!

相關文章