解析比内存

时间:2017-05-03 15:31:13

标签: json parsing memory lua lua-5.1

我正在研究的平台具有相当严格的内存限制,我正试图找到一种解析大JSON字符串的方法,而无需在内存中加载超过几百个字节。 JSON字符串存储在更大的芯片(闪存)上的文件中。

有两件事我无法找到一个好的解决方案:

  1. 通过指定foo["bar"][2]之类的“路径”来访问特定值 (如果值变成一个数组/对象,那么我们应该只返回它是一个数组/对象的事实,也许它是否为空。)
  2. 迭代JSON中的任何对象/数组。
  3. 所以基本上我需要的函数在调用时,一步一步地解析json,只保存我们实际需要继续解析的部分。

    对于界面,我认为不可能有类似exampleJson["aa"].2.["gg]的内容,但我设法非常接近:exampleJson["aa"].2.["gg"]()。这将导致调用一个函数,然后可以轻松访问{'aa',2,'gg'}并从文件中读取/解析json。

    这是我的代码到目前为止,但我真的不知道如何继续:
    https://repl.it/HfwS/2

    -- Looks complicated, but is pretty simple. Using meta tables we create a json interface that can almost be accessed as if it was a lua table.
    -- E.g. example["aa"][2]["gg"]() ; the only difference is that we have to use parentheses at the end
    -- The problematic part starts where it says `THIS IS WHERE THE JSON PARSING WOULD HAPPEN`
    json = {}
    setmetatable(json, {
        __call = function(path)
            local jsonFile = _file.open(filePath)
            local fileLen = jsonFile:stat().size
    
            local patternTable = {} -- Will store `{'aa',2,'gg'}` for `example.['aa'].[2]['gg']()`
    
            local fakeJson = {}
            setmetatable(fakeJson, { 
                __index = function (t, k)
                    patternTable[#patternTable+1] = k
                    return fakeJson
                end;
                __call = function()
    
                    -- THIS IS WHERE THE JSON PARSING WOULD HAPPEN --
    
                    -- The patternTable contains {'aa',2,'gg'} at this point 
    
                    -- Loop through the json file char by char
                    local valueToReturn = ''
                    local filePos = 0
                    for i=1, fileLen do
                        jsonFile:seek("set", filePos)
                        local currentChar = jsonFile:read(1) -- read character at current position
                        filePos = filePos + 1
                        -- print(currentChar)
    
                        -- Now the question is, how do we parse the json?
                        print('Magic to parse the json')
                        -- valueToReturn = ?
                    end
    
                    patternTable = {} -- Reset the patternTable
                    return valueToReturn
                end;
            })
          return fakeJson
        end;
    })
    
    
    local fakeParsedJson = json('example.json')
    local value = fakeParsedJson["aa"][2]["gg"]() -- Notice the `()` in the end
    
    print(value)
    

2 个答案:

答案 0 :(得分:0)

我花了一些时间思考如何实现这一目标,并最终设法将其拉下来。检索值并迭代数组/对象就像一个魅力。如果您知道更好的方法,请告诉我。 (我对代码不太满意;看起来它可能会更清晰。)但是嘿它有效。

如果您想尝试一下,这是一个小提琴: https://repl.it/HfwS/31

json = {}
setmetatable(json, {
    __call = function(filePath)
        local jsonFile = _file.open(filePath)
        local fileLen = jsonFile:stat().size

        local jsonPath = {} -- Would store `{'aa',2,'gg'}` for `example['aa'][2]['gg']()`

        local fakeJson = {}
        setmetatable(fakeJson, { 
            __index = function (t, k)
                jsonPath[#jsonPath+1] = k
                return fakeJson
            end;
            __call = function()

                -- THIS IS WHERE THE JSON PARSING WOULD HAPPEN --

                -- The jsonPath contains {'aa',2,'gg'} at this point 

                local brcStack = {} -- will be used to push/pop braces/brackets
                local jsonPathDim = 1 -- table dimension (['a'] ==  1; ['a']['b'] == 2; ...)
                -- Loop through the json file char by char
                local valueToReturn
                local filePos = 0
                local nextChar = function()
                    jsonFile:seek("set", filePos)
                    filePos = filePos + 1
                    local char = jsonFile:read(1)
                    --print(char)
                    return char
                end
                local jsonValid = true
                for o=1, fileLen do -- infinite
                    if jsonPathDim > #jsonPath then -- jsonPath followed. Now we can extract the value.
                        while true do
                            local currentChar = nextChar()
                            if currentChar == '"' then -- string
                                valueToReturn = ''
                                for i=1, fileLen do
                                    currentChar = nextChar()
                                    if currentChar == '"' then
                                        break
                                    elseif currentChar == nil then
                                        jsonValid = false
                                        break
                                    else
                                        valueToReturn = valueToReturn .. currentChar
                                    end
                                end
                                break
                            elseif string.find(currentChar,'[%d.]') then -- numbers 0.3, .3, 99 etc
                                local rawValue = ''
                                if currentChar == '.' then
                                    rawValue = '0'
                                end
                                for i=1, fileLen do
                                    if string.find(currentChar, '[%s,\r\n%]%}]') then
                                        break
                                    elseif filePos > fileLen then
                                        jsonValid = false
                                        break
                                    else
                                        rawValue = rawValue .. currentChar
                                    end
                                    currentChar = nextChar()
                                end
                                valueToReturn = tonumber(rawValue)
                                break
                            elseif currentChar == 't' then -- true
                                valueToReturn = true
                                break
                            elseif currentChar == 'f' then -- false
                                valueToReturn = false
                                break
                            elseif currentChar == 'n' then -- null
                                valueToReturn = nil -- ?
                                break
                            elseif currentChar == '{' then -- null
                                valueToReturn = {}
                                brcStack[#brcStack+1] = '{'
                                local origBrcLvl = #brcStack
                                while true do
                                    currentChar = nextChar()
                                    if filePos > fileLen then
                                        jsonValid = false
                                        break
                                    elseif currentChar == '\\' then
                                        nextChar()
                                        -- Continue
                                    elseif origBrcLvl == #brcStack and currentChar == '"' then
                                        local keyToPush = ''
                                        while true do
                                            currentChar = nextChar()
                                            if currentChar == '"' then
                                                while true do
                                                    currentChar = nextChar()
                                                    if currentChar == ':' then
                                                        valueToReturn[keyToPush] = 0
                                                        break
                                                    elseif filePos > fileLen then
                                                        break
                                                    end
                                                end
                                                break
                                            elseif filePos > fileLen then
                                                jsonValid = false
                                                break
                                            else
                                                keyToPush = keyToPush .. currentChar
                                            end
                                        end
                                        break
                                    elseif currentChar == '[' or currentChar == '{' then
                                        brcStack[#brcStack+1] = currentChar
                                    elseif currentChar == ']' then
                                        if brcStack[#brcStack] == ']' then
                                            brcStack[#brcStack] = nil
                                        else
                                            jsonValid = false
                                            break
                                        end
                                    elseif currentChar == '}' then
                                        if brcStack[#brcStack] == '}' then
                                            brcStack[#brcStack] = nil
                                        else
                                            jsonValid = false
                                            break
                                        end
                                    end
                                end
                                break
                            elseif currentChar == '[' then
                                brcStack[#brcStack+1] = '['
                                valueToReturn = {} 
                                local origBrcLvl = #brcStack
                                while true do
                                    currentChar = nextChar()

                                    if origBrcLvl == #brcStack and #valueToReturn == 0 and not string.find(currentChar, '[%s\r\n%]]') then
                                        valueToReturn[#valueToReturn+1] = 0
                                    end
                                    if filePos > fileLen then
                                        jsonValid = false
                                        break
                                    elseif currentChar == '\\' then
                                        nextChar()
                                        -- Continue
                                    elseif origBrcLvl == #brcStack and currentChar == ',' then
                                        valueToReturn[#valueToReturn+1] = 0
                                    elseif currentChar == '[' or currentChar == '{' then
                                        brcStack[#brcStack+1] = currentChar
                                    elseif currentChar == ']' then
                                        if brcStack[#brcStack] == ']' then
                                            brcStack[#brcStack] = nil
                                        else
                                            jsonValid = false
                                            break
                                        end
                                    elseif currentChar == '}' then
                                        if brcStack[#brcStack] == '}' then
                                            brcStack[#brcStack] = nil
                                        else
                                            jsonValid = false
                                            break
                                        end
                                    end
                                end
                                break
                            end
                        end
                        break
                    end
                    local currentKey = jsonPath[jsonPathDim]
                    local currentKeyLen = string.len(currentKey)
                    if type(jsonPath[jsonPathDim]) == 'string' then -- Parsing { object
                        while true do
                            local currentChar = nextChar()
                            if currentChar == '{' then
                                brcStack[#brcStack+1] = '{'
                                local origBrcLvl = #brcStack
                                local keyFound = true
                                for z=1, fileLen do -- loop over keys until we find it
                                    currentChar = nextChar()
                                    if currentChar == '\\' then
                                        nextChar()
                                        -- Continue
                                    elseif origBrcLvl == #brcStack and currentChar == '"' then
                                        local keyMatched = false
                                        for i=1, fileLen do
                                            local expectedChar = string.sub(currentKey,i,i)
                                            if nextChar() == expectedChar then
                                                if i == currentKeyLen and nextChar() == '"' then
                                                    keyMatched = true
                                                    while true do 
                                                        currentChar = nextChar()
                                                        if currentChar == ':' then
                                                            break
                                                        elseif currentChar == nil then
                                                            jsonValid = false
                                                            break
                                                        end
                                                    end
                                                    break
                                                end
                                                -- Continue
                                            else
                                                keyMatched = false
                                                break
                                            end
                                        end
                                        if keyMatched then
                                            keyFound = true
                                            break
                                        end
                                    elseif currentChar == '[' or currentChar == '{' then
                                        brcStack[#brcStack+1] = currentChar
                                    elseif currentChar == ']' then
                                        if brcStack[#brcStack] == ']' then
                                            brcStack[#brcStack] = nil
                                        else
                                            jsonValid = false
                                            break
                                        end
                                    elseif currentChar == '}' then
                                        if brcStack[#brcStack] == '}' then
                                            brcStack[#brcStack] = nil
                                        else
                                            jsonValid = false
                                            break
                                        end
                                    end
                                end
                                if keyFound then
                                    jsonPathDim = jsonPathDim+1
                                end
                                break
                            elseif currentChar == nil then
                                jsonValid = false
                                break
                            end
                        end
                    elseif type(jsonPath[jsonPathDim]) == 'number' then -- Parsing [ array
                        while true do
                            local currentChar = nextChar()
                            if currentChar == '[' then
                                brcStack[#brcStack+1] = '['
                                local origBrcLvl = #brcStack
                                local currentIndex = 1
                                -- currentKey
                                local keyMatched = true
                                for i=1, fileLen do
                                    currentChar = nextChar()
                                    if currentChar == '\\' then
                                        nextChar()
                                        -- Continue
                                    elseif origBrcLvl == #brcStack and currentChar == ',' then
                                        currentIndex = currentIndex +1
                                        if currentIndex == currentKey then
                                            jsonPathDim = jsonPathDim+1
                                            break
                                        end
                                    elseif currentChar == '[' or currentChar == '{' then
                                        brcStack[#brcStack+1] = currentChar
                                    elseif currentChar == ']' then
                                        if brcStack[#brcStack] == ']' then
                                            brcStack[#brcStack] = nil
                                        else
                                            jsonValid = false
                                            break
                                        end
                                    elseif currentChar == '}' then
                                        if brcStack[#brcStack] == '}' then
                                            brcStack[#brcStack] = nil
                                        else
                                            jsonValid = false
                                            break
                                        end
                                    else
                                        -- Continue
                                    end
                                end
                                break
                            elseif currentChar == nil then
                                jsonValid = false
                                break
                            end
                        end
                    else
                        jsonValid = false
                        break -- Invalid json
                    end
                end
                jsonPath = {} -- Reset the jsonPath
                return valueToReturn
            end;
        })
      return fakeJson
    end;
})



local example =  json('example.json')

-- Read a value
local value = example["aa"][2]['k1']()
print(value)

-- Loop over a key value table and print the keys and values
for key, value in pairs(example["aa"][2]()) do
    print('key: ' .. key, 'value: ' .. example["aa"][2][key]())
end

JSON验证可能会更好,但如果您提供无效的json数据,那么您不应该期待任何事情。

答案 1 :(得分:0)

如果要解码单个JSON元素(对象,数组等)而不是解码整个JSON,则需要具有两个功能的JSON库:

  • “遍历”功能(无需创建Lua对象的干运行解码)
  • 能够将JSON作为小部件的序列传递(而不是将整个JSON预加载为巨大的Lua字符串)。

例:
如何使用this module部分解码JSON:

-- This is content of data.txt file:
-- {"aa":["qq",{"k1":23,"gg":"YAY","Fermat_primes":[3, 5, 17, 257, 65537]}]}
-- We want to extract as Lua values only "Fermat_primes" array and "gg" string
local json = require('json')

-- Open file
local file = assert(io.open('data.txt', 'r'))

-- Define loader function which will read the file in 64-byte chunks
local function my_json_loader()
   return file:read(64)
end

local FP, gg
-- Prepare callback function for traverse with partial decode
local function my_callback (path, json_type, value)
   path = table.concat(path, '/')
   if path == "aa/2/Fermat_primes" then
      FP = value
      return true  -- we want to decode this array instead of traverse through it
   elseif path == "aa/2/gg" then 
      gg = value
   end
end

json.traverse(my_json_loader, my_callback)

-- Close file
file:close()

-- Display the results
print('aa.2.gg = '..gg)
print('aa.2.Fermat_primes:')
for k, v in ipairs(FP) do print(k, v) end

输出:

 aa.2.gg = YAY
 aa.2.Fermat_primes:
 1  3
 2  5
 3  17
 4  257
 5  65537