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.parse
和 JSON.stringify
,你就能支援一種全新的資料型別了。並且,你還可以把 xml2js
和 JSON.stringify
相結合,定製出更靈活的資料轉換器 ?
如果這些例項讓你有了點興趣,Bumpover 專案下還有一份完整的 Walkthrough,介紹如何使用 Bumpover 實現非同步遷移、及早返回、過濾節點等更靈活的特性,輔以完整的 API 文件。並且,Bumpover 雖然才開始開發不到一週,但已經實現了測試用例的 100% 程式碼覆蓋率,歡迎感興趣的同學前來體驗哦 ?
最後作為一點花絮,介紹一下筆者開發 Bumpover 的動機,以及它和 Superstruct 的淵源:Superstruct 與 Slate 富文字編輯框架師出同門,而筆者本人恰好是這個編輯器的主要貢獻者之一。Slate 在 v0.30
左右遇到了編輯器 Schema 校驗的各種問題,而 Superstruct 就是一個應運而生,允許自定義更靈活 Schema 的新輪子。而筆者在實際使用中發現 Superstruct 還能夠推廣到更一般的場景下,這就是 Bumpover 誕生的源動力了。
Bumpover 還處於非常早期的階段,非常希望各位 dalao 們能夠賞臉支援~謝謝!