JSON库

 

在进行数据传输时JSON格式目前应用广泛,因此从Lua对象与JSON字符串之间相互转换是一个非常常见的功能;目前Lua也有几个JSON库,本人用过cjson、dkjson。其中cjson的语法严格(比如unicode \u0020\u7eaf),要求符合规范否则会解析失败(如\u002),而dkjson相对宽松,当然也可以通过修改cjson的源码来完成一些特殊要求。而在使用dkjson时也没有遇到性能问题,目前使用的就是dkjson。使用时要特别注意的是大部分JSON库都仅支持UTF-8编码;因此如果你的字符编码是如GBK则需要先转换为UTF-8然后进行处理。

1.1、test_cjson.lua

1. local cjson = require("cjson")  
2.   
3. --lua对象到字符串  
4. local obj = {  
5. 1,  
6. "zhangsan",  
7.     age = nil,  
8. false,  
9. "film", "music", "read"}  
10. }  
11.   
12. local str = cjson.encode(obj)  
13. ngx.say(str, "<br/>")  
14.   
15. --字符串到lua对象  
16. str = '{"hobby":["film","music","read"],"is_male":false,"name":"zhangsan","id":1,"age":null}'
17. local obj = cjson.decode(str)  
18.   
19. ngx.say(obj.age, "<br/>")  
20. ngx.say(obj.age == nil, "<br/>")  
21. ngx.say(obj.age == cjson.null, "<br/>")  
22. ngx.say(obj.hobby[1], "<br/>")  
23.   
24.   
25. --循环引用  
26. obj = {  
27. 1
28. }  
29. obj.obj = obj  
30. -- Cannot serialise, excessive nesting  
31. --ngx.say(cjson.encode(obj), "<br/>")  
32. local cjson_safe = require("cjson.safe")  
33. --nil  
34. ngx.say(cjson_safe.encode(obj), "<br/>")  
 null将会转换为cjson.null;循环引用会抛出异常Cannot serialise, excessive nesting,默认解析嵌套深度是1000,可以通过cjson.encode_max_depth()设置深度提高性能;使用cjson.safe不会抛出异常而是返回nil。  
 
 
1.2、example.conf配置文件
 
 
 
  
1. location ~ /lua_cjson {  
2. 'text/html';  
3.    lua_code_cache on;  
4.    content_by_lua_file /usr/example/lua/test_cjson.lua;  
5. }  
   
 
1.3、访问如http://192.168.1.2/lua_cjson将得到如下结果
 
 
 
  
1. {"hobby":["film","music","read"],"is_male":false,"name":"zhangsan","id":1}  
2. null
3. false
4. true
5. film  
6. nil  
 
 
 
lua-cjson文档http://www.kyne.com.au/~mark/software/lua-cjson-manual.html。
 
接下来学习下dkjson。
 
2.1、下载dkjson库 
 
 
 
  
1. cd /usr/example/lualib/  
2. wget http://dkolf.de/src/dkjson-lua.fsl/raw/dkjson.lua?name=16cbc26080996d9da827df42cb0844a25518eeb3 -O dkjson.lua
 
 
 
2.2、test_dkjson.lua
 
 
 
  
1. local dkjson = require("dkjson")  
2.   
3. --lua对象到字符串  
4. local obj = {  
5. 1,  
6. "zhangsan",  
7.     age = nil,  
8. false,  
9. "film", "music", "read"}  
10. }  
11.   
12. local str = dkjson.encode(obj, {indent = true})  
13. ngx.say(str, "<br/>")  
14.   
15. --字符串到lua对象  
16. str = '{"hobby":["film","music","read"],"is_male":false,"name":"zhangsan","id":1,"age":null}'
17. local obj, pos, err = dkjson.decode(str, 1, nil)  
18.   
19. ngx.say(obj.age, "<br/>")  
20. ngx.say(obj.age == nil, "<br/>")  
21. ngx.say(obj.hobby[1], "<br/>")  
22.   
23. --循环引用  
24. obj = {  
25. 1
26. }  
27. obj.obj = obj  
28. --reference cycle  
29. --ngx.say(dkjson.encode(obj), "<br/>")                                       
 
2.3、example.conf配置文件
 
 
 
  
1. location ~ /lua_dkjson {  
2. 'text/html';  
3.    lua_code_cache on;  
4.    content_by_lua_file /usr/example/lua/test_dkjson.lua;  
5. }  
   
 
2.4、访问如http://192.168.1.2/lua_dkjson将得到如下结果
 
 
 
  
1. { "hobby":["film","music","read"], "is_male":false, "name":"zhangsan", "id":1
2. nil  
3. true
4. film  
 
 
dkjson文档http://dkolf.de/src/dkjson-lua.fsl/home和http://dkolf.de/src/dkjson-lua.fsl/wiki?name=Documentation。
 
编码转换
我们在使用一些类库时会发现大部分库仅支持UTF-8编码,因此如果使用其他编码的话就需要进行编码转换的处理;而Linux上最常见的就是iconv,而lua-iconv就是它的一个Lua API的封装。
 
安装lua-iconv可以通过如下两种方式:
ubuntu下可以使用如下方式
 
 
 
  
1. apt-get install luarocks  
2. luarocks install lua-iconv   
3. cp /usr/local/lib/lua/5.1/iconv.so  /usr/example/lualib/  
 
 
源码安装方式,需要有gcc环境
 
 
 
  
1. wget https://github.com/do^Cloads/ittner/lua-iconv/lua-iconv-7.tar.gz
2. tar -xvf lua-iconv-7.tar.gz  
3. cd lua-iconv-7
4. gcc -O2 -fPIC -I/usr/include/lua5.1
5. gcc -shared -o iconv.so -L/usr/local/lib luaiconv.o -L/usr/lib  
6. cp iconv.so  /usr/example/lualib/  
 
 
  
1、test_iconv.lua
 
 
 
  
1. ngx.say("中文")  
 
 
此时文件编码必须为UTF-8,即Lua文件编码为什么里边的字符编码就是什么。
  
2、example.conf配置文件
 
 
 
  
1. location ~ /lua_iconv {  
2. 'text/html';  
3.    charset gbk;  
4.    lua_code_cache on;  
5.    content_by_lua_file /usr/example/lua/test_iconv.lua;  
6. }  
 
 
通过charset告诉浏览器我们的字符编码为gbk。  
 
3、访问 http://192.168.1.2/lua_iconv会发现输出乱码;
 
此时需要我们将test_iconv.lua中的字符进行转码处理:
 
 
 
  
1. local iconv = require("iconv")  
2. local togbk = iconv.new("gbk", "utf-8")  
3. local str, err = togbk:iconv("中文")  
4. ngx.say(str)  
 
 
通过转码我们得到最终输出的内容编码为gbk, 使用方式iconv.new(目标编码, 源编码)。
 
有如下可能出现的错误:
 
 
 
  
1. nil     
2.     没有错误成功。  
3. iconv.ERROR_NO_MEMORY  
4.     内存不足。  
5. iconv.ERROR_INVALID  
6.     有非法字符。  
7. iconv.ERROR_INCOMPLETE  
8.     有不完整字符。  
9. iconv.ERROR_FINALIZED  
10.     使用已经销毁的转换器,比如垃圾回收了。  
11. iconv.ERROR_UNKNOWN   
12.     未知错误  
 
 
 
iconv在转换时遇到非法字符或不能转换的字符就会失败,此时可以使用如下方式忽略转换失败的字符
 
 
 
  
1. local togbk_ignore = iconv.new("GBK//IGNORE", "UTF-8")  
 
 
 
另外在实际使用中进行UTF-8到GBK转换过程时,会发现有些字符在GBK编码表但是转换不了,此时可以使用更高的编码GB18030来完成转换。 
 
更多介绍请参考http://ittner.github.io/lua-iconv/。
 
位运算
Lua 5.3之前是没有提供位运算支持的,需要使用第三方库,比如LuaJIT提供了bit库。
1、test_bit.lua 
 
 
 
  
1. local bit = require("bit")  
2. ngx.say(bit.lshift(1, 2))  
 lshift进行左移位运算,即得到4。 
 
  
其他位操作API请参考http://bitop.luajit.org/api.html。Lua 5.3的位运算操作符http://cloudwu.github.io/lua53doc/manual.html#3.4.2. 
 
cache
ngx_lua模块本身提供了全局共享内存ngx.shared.DICT可以实现全局共享,另外可以使用如Redis来实现缓存。另外还一个lua-resty-lrucache实现,其和ngx.shared.DICT不一样的是它是每Worker进程共享,即每个Worker进行会有一份缓存,而且经过实际使用发现其性能不如ngx.shared.DICT。但是其好处就是不需要进行全局配置。
 
1、创建缓存模块来实现只初始化一次:
 
 
 
  
1. vim /usr/example/lualib/mycache.lua   
 
 
 
  
1. local lrucache = require("resty.lrucache")  
2. --创建缓存实例,并指定最多缓存多少条目  
3. local cache, err = lrucache.new(200)  
4. if
5. "create cache error : ", err)  
6. end  
7.   
8. local function set(key, value, ttlInSeconds)  
9.     cache:set(key, value, ttlInSeconds)  
10. end  
11.   
12. local function get(key)  
13. return
14. end  
15.   
16. local _M = {  
17.   set = set,  
18.   get = get  
19. }  
20.   
21. return
 
 
此处利用了模块的特性实现了每个Worker进行只初始化一次cache实例。
 
2、test_lrucache.lua  
 
 
 
  
1. local mycache = require("mycache")  
2. local count = mycache.get("count") or 0
3. count = count + 1
4. mycache.set("count", count, 10 * 60 * 60) --10分钟  
5. ngx.say(mycache.get("count"))                
 
 
可以实现诸如访问量统计,但仅是每Worker进程的。   
 
3、example.conf配置文件
 
 
 
  
1. location ~ /lua_lrucache {  
2. 'text/html';  
3.    lua_code_cache on;  
4.    content_by_lua_file /usr/example/lua/test_lrucache.lua;  
5. }  
 
 
访问如http://192.168.1.2/lua_lrucache测试。
 
更多介绍请参考https://github.com/openresty/lua-resty-lrucache。
 
字符串处理
Lua 5.3之前没有提供字符操作相关的函数,如字符串截取、替换等都是字节为单位操作;在实际使用时尤其包含中文的场景下显然不能满足需求;即使Lua 5.3也仅提供了基本的UTF-8操作。
 
Lua UTF-8库
https://github.com/starwing/luautf8
 
LuaRocks安装
 
 
 
  
1. #首先确保git安装了  
2. apt-get install git  
3. luarocks install utf8  
4. cp /usr/local/lib/lua/5.1/utf8.so  /usr/example/lualib/  
 
 
 
源码安装
 
 
 
  
1. wget https://github.com/starwing/luautf8/archive/master.zip
2. unzip master.zip  
3. cd luautf8-master/  
4. gcc -O2 -fPIC -I/usr/include/lua5.1
5. gcc -shared -o utf8.so -L/usr/local/lib utf8.o -L/usr/lib  
 
 
 
1、test_utf8.lua
 
 
 
  
1. local utf8 = require("utf8")  
2. local str = "abc中文"
3. ngx.say("len : ", utf8.len(str), "<br/>")  
4. ngx.say("sub : ", utf8.sub(str, 1, 4))  
 

  2、example.conf配置文件 

 
 
  
1. location ~ /lua_utf8 {  
2. 'text/html';  
3.    lua_code_cache on;  
4.    content_by_lua_file /usr/example/lua/test_utf8.lua;  
5. }  
    
 
3、访问如http://192.168.1.2/lua_utf8测试得到如下结果
len : 5
 sub : abc中 
字符串转换为unicode编码:
 
 
 
  
1. local bit = require("bit")  
2. local bit_band = bit.band  
3. local bit_bor = bit.bor  
4. local bit_lshift = bit.lshift  
5. local string_format = string.format  
6. local string_byte = string.byte
7. local table_concat = table.concat  
8.   
9. local function utf8_to_unicode(str)  
10. if not str or str == "" or str == ngx.null
11. return
12.     end  
13. 0, nil  
14. for i = 1, #str do
15.         local c = string_byte(str, i)  
16. if seq == 0
17. if
18. 1] = string_format("%04x", val)  
19.             end  
20.   
21. 0x80 and 1 or c < 0xE0 and 2 or c < 0xF0 and 3
22. 0xF8 and 4 or --c < 0xFC and 5 or c < 0xFE and 6
23. 0
24. if seq == 0
25. 'invalid UTF-8 character sequence' .. ",,,"
26. return
27.             end  
28.   
29. 2 ^ (8 - seq) - 1)  
30. else
31. 6), bit_band(c, 0x3F))  
32.         end  
33. 1
34.     end  
35. if
36. 1] = string_format("%04x", val)  
37.     end  
38. if #res == 0
39. return
40.     end  
41. return "\\u" .. table_concat(res, "\\u")  
42. end  
43.   
44. ngx.say("utf8 to unicode : ", utf8_to_unicode("abc中文"), "<br/>")  
 
 
如上方法将输出utf8 to unicode : \u0061\u0062\u0063\u4e2d\u6587。
 
删除空格:
 
 
 
  
1. local function ltrim(s)  
2. if
3. return
4.     end  
5.     local res = s  
6. '%S')  
7. if
8. ''
9. 1
10.         res = string_sub(res, tmp)  
11.     end  
12. return
13. end  
14.   
15. local function rtrim(s)  
16. if
17. return
18.     end  
19.     local res = s  
20. '%S%s*$')  
21. if
22. ''
23.     elseif tmp ~= #res then  
24. 1, tmp)  
25.     end  
26.   
27. return
28. end  
29.   
30. local function trim(s)  
31. if
32. return
33.     end  
34.     local res1 = ltrim(s)  
35.     local res2 = rtrim(res1)  
36. return
37. end  
 
 
 
字符串分割:
 
 
 
  
1. function split(szFullString, szSeparator)  
2. 1
3. 1
4.     local nSplitArray = {}  
5. while true do
6.        local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex)  
7. if
8.         nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString))  
9. break
10.        end  
11. 1)  
12.        nFindStartIndex = nFindLastIndex + string.len(szSeparator)  
13. 1
14.     end  
15. return
16. end


如split("a,b,c", ",") 将得到一个分割后的table。

 

到此基本的字符串操作就完成了,其他luautf8模块的API和LuaAPI类似可以参考

http://cloudwu.github.io/lua53doc/manual.html#6.4

http://cloudwu.github.io/lua53doc/manual.html#6.5

 

另外对于GBK的操作,可以先转换为UTF-8,最后再转换为GBK即可。