THRIFT-3631 JSON protocol implement for thrift-lua library.

THRIFT-3631 Lua-This is JSON protocol implement for thrift-lua library.
TJsonProtocol.lua is new source of lua for thrift JSON protocol.
diff --git a/lib/lua/TJsonProtocol.lua b/lib/lua/TJsonProtocol.lua
new file mode 100644
index 0000000..db08eec
--- /dev/null
+++ b/lib/lua/TJsonProtocol.lua
@@ -0,0 +1,727 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"), you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+--   http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+require 'TProtocol'
+require 'libluabpack'
+require 'libluabitwise'
+
+TJSONProtocol = __TObject.new(TProtocolBase, {
+  __type = 'TJSONProtocol',
+  THRIFT_JSON_PROTOCOL_VERSION = 1,
+  jsonContext = {},
+  jsonContextVal = {first = true, colon = true, ttype = 2, null = true},
+  jsonContextIndex = 1,
+  hasReadByte = ""
+})
+
+TTypeToString = {}
+TTypeToString[TType.BOOL]   = "tf"
+TTypeToString[TType.BYTE]   = "i8"
+TTypeToString[TType.I16]    = "i16"
+TTypeToString[TType.I32]    = "i32"
+TTypeToString[TType.I64]    = "i64"
+TTypeToString[TType.DOUBLE] = "dbl"
+TTypeToString[TType.STRING] = "str"
+TTypeToString[TType.STRUCT] = "rec"
+TTypeToString[TType.LIST]   = "lst"
+TTypeToString[TType.SET]    = "set"
+TTypeToString[TType.MAP]    = "map"
+
+StringToTType = {
+  tf  = TType.BOOL,
+  i8  = TType.BYTE,
+  i16 = TType.I16,
+  i32 = TType.I32,
+  i64 = TType.I64,
+  dbl = TType.DOUBLE,
+  str = TType.STRING,
+  rec = TType.STRUCT,
+  map = TType.MAP,
+  set = TType.SET,
+  lst = TType.LIST
+}
+
+JSONNode = {
+  ObjectBegin = '{',
+  ObjectEnd = '}',
+  ArrayBegin = '[',
+  ArrayEnd = ']',
+  PairSeparator = ':',
+  ElemSeparator = ',',
+  Backslash = '\\',
+  StringDelimiter = '"',
+  ZeroChar = '0',
+  EscapeChar = 'u',
+  Nan = 'NaN',
+  Infinity = 'Infinity',
+  NegativeInfinity = '-Infinity',
+  EscapeChars = "\"\\bfnrt",
+  EscapePrefix = "\\u00"
+}
+
+EscapeCharVals = {
+  '"', '\\', '\b', '\f', '\n', '\r', '\t'
+}
+
+JSONCharTable = {
+  --0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+    0,  0,  0,  0,  0,  0,  0,  0, 98,116,110,  0,102,114,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    1,  1,34,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+}
+
+-- character table string
+local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+
+-- encoding
+function base64_encode(data)
+    return ((data:gsub('.', function(x) 
+        local r,b='',x:byte()
+        for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
+        return r;
+    end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
+        if (#x < 6) then return '' end
+        local c=0
+        for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
+        return b:sub(c+1,c+1)
+    end)..({ '', '==', '=' })[#data%3+1])
+end
+
+-- decoding
+function base64_decode(data)
+    data = string.gsub(data, '[^'..b..'=]', '')
+    return (data:gsub('.', function(x)
+        if (x == '=') then return '' end
+        local r,f='',(b:find(x)-1)
+        for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
+        return r;
+    end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
+        if (#x ~= 8) then return '' end
+        local c=0
+        for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
+        return string.char(c)
+    end))
+end
+
+function TJSONProtocol:resetContext()
+  self.jsonContext = {}
+  self.jsonContextVal = {first = true, colon = true, ttype = 2, null = true}
+  self.jsonContextIndex = 1
+end
+
+function TJSONProtocol:contextPush(context)
+  self.jsonContextIndex = self.jsonContextIndex + 1
+  self.jsonContext[self.jsonContextIndex] = self.jsonContextVal
+  self.jsonContextVal = context
+end
+
+function TJSONProtocol:contextPop()
+  self.jsonContextVal = self.jsonContext[self.jsonContextIndex]
+  self.jsonContextIndex = self.jsonContextIndex - 1
+end
+
+function TJSONProtocol:escapeNum()
+  if self.jsonContextVal.ttype == 1 then
+    return self.jsonContextVal.colon
+  else
+    return false
+  end
+end
+
+function TJSONProtocol:writeElemSeparator()
+  if self.jsonContextVal.null then
+    return
+  end
+  if self.jsonContextVal.first then
+    self.jsonContextVal.first = false
+  else
+    if self.jsonContextVal.ttype == 1 then
+      if self.jsonContextVal.colon then
+        self.trans:write(JSONNode.PairSeparator)
+        self.jsonContextVal.colon = false
+      else
+        self.trans:write(JSONNode.ElemSeparator)
+        self.jsonContextVal.colon = true
+      end
+    else
+      self.trans:write(JSONNode.ElemSeparator)
+    end
+  end
+end
+
+function TJSONProtocol:hexChar(val)
+  val = libluabitwise.band(val, 0x0f)
+  if val < 10 then
+    return val + 48
+  else
+    return val + 87
+  end
+end
+
+function TJSONProtocol:writeJSONEscapeChar(ch)
+  self.trans:write(JSONNode.EscapePrefix)
+  local outCh = hexChar(libluabitwise.shiftr(ch, 4))
+  local buff = libluabpack.bpack('c', outCh)
+  self.trans:write(buff)
+  outCh = hexChar(ch)
+  buff = libluabpack.bpack('c', outCh)
+  self.trans:write(buff)
+end
+
+function TJSONProtocol:writeJSONChar(byte)
+  ch = string.byte(byte)
+  if ch >= 0x30 then
+    if ch == JSONNode.Backslash then
+      self.trans:write(JSONNode.Backslash)
+      self.trans:write(JSONNode.Backslash)
+    else
+      self.trans:write(byte)
+    end
+  else
+    local outCh = JSONCharTable[ch+1]
+    if outCh == 1 then
+      self.trans:write(byte)
+    elseif outCh > 1 then
+      self.trans:write(JSONNode.Backslash)
+      local buff = libluabpack.bpack('c', outCh)
+      self.trans:write(buff)
+    else
+      self:writeJSONEscapeChar(ch)
+    end
+  end
+end
+
+function TJSONProtocol:writeJSONString(str)
+  self:writeElemSeparator()
+  self.trans:write(JSONNode.StringDelimiter)
+  -- TODO escape special characters
+  local length = string.len(str)
+  local ii = 1
+  while ii <= length do
+    self:writeJSONChar(string.sub(str, ii, ii))
+    ii = ii + 1
+  end
+  self.trans:write(JSONNode.StringDelimiter)
+end
+
+function TJSONProtocol:writeJSONBase64(str)
+  self:writeElemSeparator()
+  self.trans:write(JSONNode.StringDelimiter)
+  local length = string.len(str)
+  local offset = 1
+  while length >= 3 do
+    -- Encode 3 bytes at a time
+    local bytes = base64_encode(string.sub(str, offset, offset+3))
+    self.trans:write(bytes)
+    length = length - 3
+    offset = offset + 3
+  end
+  if length > 0 then
+    local bytes = base64_encode(string.sub(str, offset, offset+length))
+    self.trans:write(bytes)
+  end
+  self.trans:write(JSONNode.StringDelimiter)
+end
+
+function TJSONProtocol:writeJSONInteger(num)
+  self:writeElemSeparator()
+  if self:escapeNum() then
+    self.trans:write(JSONNode.StringDelimiter)
+  end
+  local numstr = "" .. num
+  numstr = string.sub(numstr, string.find(numstr, "^[+-]?%d+"))
+  self.trans:write(numstr)
+  if self:escapeNum() then
+    self.trans:write(JSONNode.StringDelimiter)
+  end
+end
+
+function TJSONProtocol:writeJSONDouble(dub)
+  self:writeElemSeparator()
+  local val = "" .. dub
+  local prefix = string.sub(val, 1, 1)
+  local special = false
+  if prefix == 'N' or prefix == 'n' then
+    val = JSONNode.Nan
+    special = true
+  elseif prefix == 'I' or prefix == 'i' then
+    val = JSONNode.Infinity
+    special = true
+  elseif prefix == '-' then
+    local secondByte = string.sub(val, 2, 2)
+    if secondByte == 'I' or secondByte == 'i' then
+      val = JSONNode.NegativeInfinity
+      special = true
+    end
+  end
+
+  if special or self:escapeNum() then
+    self.trans:write(JSONNode.StringDelimiter)
+  end
+  self.trans:write(val)
+  if special or self:escapeNum() then
+    self.trans:write(JSONNode.StringDelimiter)
+  end
+end
+
+function TJSONProtocol:writeJSONObjectBegin()
+  self:writeElemSeparator()
+  self.trans:write(JSONNode.ObjectBegin)
+  self:contextPush({first = true, colon = true, ttype = 1, null = false})
+end
+
+function TJSONProtocol:writeJSONObjectEnd()
+  self:contextPop()
+  self.trans:write(JSONNode.ObjectEnd)
+end
+
+function TJSONProtocol:writeJSONArrayBegin()
+  self:writeElemSeparator()
+  self.trans:write(JSONNode.ArrayBegin)
+  self:contextPush({first = true, colon = true, ttype = 2, null = false})
+end
+
+function TJSONProtocol:writeJSONArrayEnd()
+  self:contextPop()
+  self.trans:write(JSONNode.ArrayEnd)
+end
+
+function TJSONProtocol:writeMessageBegin(name, ttype, seqid)
+  self:resetContext()
+  self:writeJSONArrayBegin()
+  self:writeJSONInteger(TJSONProtocol.THRIFT_JSON_PROTOCOL_VERSION)
+  self:writeJSONString(name)
+  self:writeJSONInteger(ttype)
+  self:writeJSONInteger(seqid)
+end
+
+function TJSONProtocol:writeMessageEnd()
+  self:writeJSONArrayEnd()
+end
+
+function TJSONProtocol:writeStructBegin(name)
+  self:writeJSONObjectBegin()
+end
+
+function TJSONProtocol:writeStructEnd()
+  self:writeJSONObjectEnd()
+end
+
+function TJSONProtocol:writeFieldBegin(name, ttype, id)
+  self:writeJSONInteger(id)
+  self:writeJSONObjectBegin()
+  self:writeJSONString(TTypeToString[ttype])
+end
+
+function TJSONProtocol:writeFieldEnd()
+  self:writeJSONObjectEnd()
+end
+
+function TJSONProtocol:writeFieldStop()
+end
+
+function TJSONProtocol:writeMapBegin(ktype, vtype, size)
+  self:writeJSONArrayBegin()
+  self:writeJSONString(TTypeToString[ktype])
+  self:writeJSONString(TTypeToString[vtype])
+  self:writeJSONInteger(size)
+  return self:writeJSONObjectBegin()
+end
+
+function TJSONProtocol:writeMapEnd()
+  self:writeJSONObjectEnd()
+  self:writeJSONArrayEnd()
+end
+
+function TJSONProtocol:writeListBegin(etype, size)
+  self:writeJSONArrayBegin()
+  self:writeJSONString(TTypeToString[etype])
+  self:writeJSONInteger(size)
+end
+
+function TJSONProtocol:writeListEnd()
+  self:writeJSONArrayEnd()
+end
+
+function TJSONProtocol:writeSetBegin(etype, size)
+  self:writeJSONArrayBegin()
+  self:writeJSONString(TTypeToString[etype])
+  self:writeJSONInteger(size)
+end
+
+function TJSONProtocol:writeSetEnd()
+  self:writeJSONArrayEnd()
+end
+
+function TJSONProtocol:writeBool(bool)
+  if bool then
+    self:writeJSONInteger(1)
+  else
+    self:writeJSONInteger(0)
+  end
+end
+
+function TJSONProtocol:writeByte(byte)
+  local buff = libluabpack.bpack('c', byte)
+  local val = libluabpack.bunpack('c', buff)
+  self:writeJSONInteger(val)
+end
+
+function TJSONProtocol:writeI16(i16)
+  local buff = libluabpack.bpack('s', i16)
+  local val = libluabpack.bunpack('s', buff)
+  self:writeJSONInteger(val)
+end
+
+function TJSONProtocol:writeI32(i32)
+  local buff = libluabpack.bpack('i', i32)
+  local val = libluabpack.bunpack('i', buff)
+  self:writeJSONInteger(val)
+end
+
+function TJSONProtocol:writeI64(i64)
+  local buff = libluabpack.bpack('l', i64)
+  local val = libluabpack.bunpack('l', buff)
+  self:writeJSONInteger(tostring(val))
+end
+
+function TJSONProtocol:writeDouble(dub)
+  self:writeJSONDouble(string.format("%.16f", dub))
+end
+
+function TJSONProtocol:writeString(str)
+  self:writeJSONString(str)
+end
+
+function TJSONProtocol:writeBinary(str)
+  -- Should be utf-8
+  self:writeJSONBase64(str)
+end
+
+function TJSONProtocol:readJSONSyntaxChar(ch)
+  local ch2 = ""
+  if self.hasReadByte ~= "" then
+    ch2 = self.hasReadByte
+    self.hasReadByte = ""
+  else
+    ch2 = self.trans:readAll(1)
+  end
+  if ch2 ~= ch then
+    terror(TProtocolException:new{message = "Expected ".. ch .. ", got " .. ch2})
+  end
+end
+
+function TJSONProtocol:readElemSeparator()
+  if self.jsonContextVal.null then
+    return
+  end
+  if self.jsonContextVal.first then
+    self.jsonContextVal.first = false
+  else
+    if self.jsonContextVal.ttype == 1 then
+      if self.jsonContextVal.colon then
+        self:readJSONSyntaxChar(JSONNode.PairSeparator)
+        self.jsonContextVal.colon = false
+      else
+        self:readJSONSyntaxChar(JSONNode.ElemSeparator)
+        self.jsonContextVal.colon = true
+      end
+    else
+      self:readJSONSyntaxChar(JSONNode.ElemSeparator)
+    end
+  end
+end
+
+function TJSONProtocol:hexVal(ch)
+  local val = string.byte(ch)
+  if val >= 48 and val <= 57 then
+    return val - 48
+  elseif val >= 97 and val <= 102 then
+    return val - 87
+  else
+    terror(TProtocolException:new{message = "Expected hex val ([0-9a-f]); got " .. ch})
+  end
+end
+
+function TJSONProtocol:readJSONEscapeChar(ch)
+  self:readJSONSyntaxChar(JSONNode.ZeroChar)
+  self:readJSONSyntaxChar(JSONNode.ZeroChar)
+  local b1 = self.trans:readAll(1)
+  local b2 = self.trans:readAll(1)
+  return libluabitwise.shiftl(self:hexVal(b1), 4) + self:hexVal(b2)
+end
+
+
+function TJSONProtocol:readJSONString()
+  self:readElemSeparator()
+  self:readJSONSyntaxChar(JSONNode.StringDelimiter)
+  local result = ""
+  while true do
+    local ch = self.trans:readAll(1)
+    if ch == JSONNode.StringDelimiter then
+      break
+    end
+    if ch == JSONNode.Backslash then
+      ch = self.trans:readAll(1)
+      if ch == JSONNode.EscapeChar then
+        self:readJSONEscapeChar(ch)
+      else
+        local pos, _ = string.find(JSONNode.EscapeChars, ch)
+        if pos == nil then
+          terror(TProtocolException:new{message = "Expected control char, got " .. ch})
+        end
+        ch = EscapeCharVals[pos]
+      end
+    end
+    result = result .. ch
+  end
+  return result
+end
+
+function TJSONProtocol:readJSONBase64()
+  local result = self:readJSONString()
+  local length = string.len(result)
+  local str = ""
+  local offset = 1
+  while length >= 4 do
+    local bytes = string.sub(result, offset, offset+4)
+    str = str .. base64_decode(bytes)
+    offset = offset + 4
+    length = length - 4
+  end
+  if length >= 0 then
+    str = str .. base64_decode(string.sub(result, offset, offset + length))
+  end
+  return str
+end
+
+function TJSONProtocol:readJSONNumericChars()
+  local result = ""
+  while true do
+    local ch = self.trans:readAll(1)
+    if string.find(ch, '[-+0-9.Ee]') then
+      result = result .. ch
+    else
+      self.hasReadByte = ch
+      break
+    end
+  end
+  return result
+end
+
+function TJSONProtocol:readJSONLongInteger()
+  self:readElemSeparator()
+  if self:escapeNum() then
+    self:readJSONSyntaxChar(JSONNode.StringDelimiter)
+  end
+  local result = self:readJSONNumericChars()
+  if self:escapeNum() then
+    self:readJSONSyntaxChar(JSONNode.StringDelimiter)
+  end
+  return result
+end
+
+function TJSONProtocol:readJSONInteger()
+  return tonumber(self:readJSONLongInteger())
+end
+
+function TJSONProtocol:readJSONDouble()
+  self:readElemSeparator()
+  local delimiter = self.trans:readAll(1)
+  local num = 0.0
+  if delimiter == JSONNode.StringDelimiter then
+    local str = self:readJSONString()
+    if str == JSONNode.Nan then
+      num = 1.0
+    elseif str == JSONNode.Infinity then
+      num = math.maxinteger
+    elseif str == JSONNode.NegativeInfinity then
+      num = math.mininteger
+    else
+      num = tonumber(str)
+    end
+  else
+    if self:escapeNum() then
+      self:readJSONSyntaxChar(JSONNode.StringDelimiter)
+    end
+    local result = self:readJSONNumericChars()
+    num = tonumber(delimiter.. result)
+  end
+  return num
+end
+
+function TJSONProtocol:readJSONObjectBegin()
+  self:readElemSeparator()
+  self:readJSONSyntaxChar(JSONNode.ObjectBegin)
+  self:contextPush({first = true, colon = true, ttype = 1, null = false})
+end
+
+function TJSONProtocol:readJSONObjectEnd()
+  self:readJSONSyntaxChar(JSONNode.ObjectEnd)
+  self:contextPop()
+end
+
+function TJSONProtocol:readJSONArrayBegin()
+  self:readElemSeparator()
+  self:readJSONSyntaxChar(JSONNode.ArrayBegin)
+  self:contextPush({first = true, colon = true, ttype = 2, null = false})
+end
+
+function TJSONProtocol:readJSONArrayEnd()
+  self:readJSONSyntaxChar(JSONNode.ArrayEnd)
+  self:contextPop()
+end
+
+function TJSONProtocol:readMessageBegin()
+  self:resetContext()
+  self:readJSONArrayBegin()
+  local version = self:readJSONInteger()
+  if version ~= self.THRIFT_JSON_PROTOCOL_VERSION then
+    terror(TProtocolException:new{message = "Message contained bad version."})
+  end
+  local name = self:readJSONString()
+  local ttype = self:readJSONInteger()
+  local seqid = self:readJSONInteger()
+  return name, ttype, seqid
+end
+
+function TJSONProtocol:readMessageEnd()
+  self:readJSONArrayEnd()
+end
+
+function TJSONProtocol:readStructBegin()
+  self:readJSONObjectBegin()
+  return nil
+end
+
+function TJSONProtocol:readStructEnd()
+  self:readJSONObjectEnd()
+end
+
+function TJSONProtocol:readFieldBegin()
+  local ttype = TType.STOP
+  local id = 0
+  local ch = self.trans:readAll(1)
+  self.hasReadByte = ch
+  if ch ~= JSONNode.ObjectEnd then
+    id = self:readJSONInteger()
+    self:readJSONObjectBegin()
+    local typeName = self:readJSONString()
+    ttype = StringToTType[typeName]
+  end
+  return nil, ttype, id
+end
+
+function TJSONProtocol:readFieldEnd()
+  self:readJSONObjectEnd()
+end
+
+function TJSONProtocol:readMapBegin()
+  self:readJSONArrayBegin()
+  local typeName = self:readJSONString()
+  local ktype = StringToTType[typeName]
+  typeName = self:readJSONString()
+  local vtype = StringToTType[typeName]
+  local size = self:readJSONInteger()
+  self:readJSONObjectBegin()
+  return ktype, vtype, size
+end
+
+function TJSONProtocol:readMapEnd()
+  self:readJSONObjectEnd()
+  self:readJSONArrayEnd()
+end
+
+function TJSONProtocol:readListBegin()
+  self:readJSONArrayBegin()
+  local typeName = self:readJSONString()
+  local etype = StringToTType[typeName]
+  local size = self:readJSONInteger()
+  return etype, size
+end
+
+function TJSONProtocol:readListEnd()
+  return self:readJSONArrayEnd()
+end
+
+function TJSONProtocol:readSetBegin()
+  return self:readListBegin()
+end
+
+function TJSONProtocol:readSetEnd()
+  return self:readJSONArrayEnd()
+end
+
+function TJSONProtocol:readBool()
+  local result = self:readJSONInteger()
+  if result == 1 then
+    return true
+  else
+    return false
+  end
+end
+
+function TJSONProtocol:readByte()
+  local result = self:readJSONInteger()
+  if result >= 256 then
+    terror(TProtocolException:new{message = "UnExpected Byte " .. result})
+  end
+  return result
+end
+
+function TJSONProtocol:readI16()
+  return self:readJSONInteger()
+end
+
+function TJSONProtocol:readI32()
+  return self:readJSONInteger()
+end
+
+function TJSONProtocol:readI64()
+  local long = liblualongnumber.new
+  return long(self:readJSONLongInteger())
+end
+
+function TJSONProtocol:readDouble()
+  return self:readJSONDouble()
+end
+
+function TJSONProtocol:readString()
+  return self:readJSONString()
+end
+
+function TJSONProtocol:readBinary()
+  return self:readJSONBase64()
+end
+
+TJSONProtocolFactory = TProtocolFactory:new{
+  __type = 'TJSONProtocolFactory',
+}
+
+function TJSONProtocolFactory:getProtocol(trans)
+  -- TODO Enforce that this must be a transport class (ie not a bool)
+  if not trans then
+    terror(TProtocolException:new{
+      message = 'Must supply a transport to ' .. ttype(self)
+    })
+  end
+  return TJSONProtocol:new{
+    trans = trans
+  }
+end
diff --git a/test/lua/test_basic_client.lua b/test/lua/test_basic_client.lua
index b89f25d..dcaca29 100644
--- a/test/lua/test_basic_client.lua
+++ b/test/lua/test_basic_client.lua
@@ -20,6 +20,7 @@
 require('TBufferedTransport')
 require('TFramedTransport')
 require('TCompactProtocol')
+require('TJsonProtocol')
 require('TBinaryProtocol')
 require('ThriftTest_ThriftTest')
 require('liblualongnumber')
@@ -73,6 +74,7 @@
   local protocols = {
     binary = TBinaryProtocol,
     compact = TCompactProtocol,
+    json = TJSONProtocol,
   }
   assert(protocols[opt.protocol] ~= nil)
   local protocol = protocols[opt.protocol]:new{
diff --git a/test/tests.json b/test/tests.json
index 902d2fe..0aaad14 100644
--- a/test/tests.json
+++ b/test/tests.json
@@ -510,7 +510,8 @@
       ],
       "protocols": [
         "binary",
-        "compact"
+        "compact",
+        "json"
       ],
       "command": [
         "lua",