正確的序列化 Lua 中帶元表的對象
在 Lua 5.2 之后的版本,約定了在元表中可以給出一個 __pairs 方法,而 lua 的基礎庫 pairs 會使用這個元方法來迭代一個對象。
Lua 5.3 之后的版本,取消了 lua 5.2 中的 __ipairs 約定,而統一使用 lua_geti 來訪問整數為索引的數組。
可惜的是,許多 lua 序列化庫對此支持的并不好。今天我在改進 bson 的序列化庫時,重新考慮了這個問題,看看這個序列化過程怎么做,才能更好的支持 lua 5.3 以后的約定。
在 skynet 中重新實現的 bson 庫是這樣做的:
和 json 不同,bson 在規范中嚴格區分了數組和字典。
那么,序列化時,應首先判斷一個 table 是否有 __len 元方法,如果有,則表示它是一個數組。因為我們不支持把一個需要傳入 bson 序列化的 table 同時當成數組和字典使用,如果實現了 __len 元方法,那么顯然是希望把它看作一個數組。
然后,判斷這個 table 是否有 __pairs 元方法。如果有,這表示它是一個字典,需要用這個元方法迭代它。
如果兩個元方法都沒有,那么這個 table 是個原生表,需要用額外的方法探測它到底是一個數組還是一個字典。這里采用的方法是,使用 lua_rawlen 獲取一下數組部分的長度。然后調用 lua_next 傳入最后一個數字索引,探測是否有其它的 key 。
這種方法在 lua 的文檔中并沒有嚴格約定,它依賴 lua 的實現。目前官方 lua 的實現中, lua_next 總是先迭代完數組部分,再迭代 hash 部分的。這樣實現最為便捷,所以看起來也不會修改。而嚴格的檢測方法則應該是用 lua_next 迭代所有的 key ,逐個判斷它們都是否是數字,且是否連續。我不想采用這種開銷 O(n) 的嚴格算法,前面提到的 O(1) 的取巧方法實際工作的很好。
這種,我們對 table 分了三種類別:數組、帶元表的字典、原生字典。
數組這個類型不必區分是原生數組還是帶元表的,而只需要取出長度(使用 __len 或調用 lua_rawlen ),然后用 lua_geti 逐個調用。如果發現數組中有空洞(value 為 nil ),序列化過程會拋出 error 。(這點對之前的實現是一個改進,老的版本并不能檢測出數組中的空洞)
帶元表的字典采用 __pairs 方法進行迭代,而原生字典可以直接用 lua_next 迭代。
來自: http://blog.codingnow.com/2016/06/seri_lua_object.html