将Lua数据转换为JSON

9
这个EPGP魔兽世界插件生成一个epgp.lua数据库文件。
我写了一个插件,将Lua数据转换为JSON对象以在公会网站上显示。它可以在早期版本的插件中使用,但现在我尝试将其正确转换为文件时遇到了问题。以下是两个代码片段,显示了转换问题-请参见此演示
第一个可以很好地形成嵌套数组:
["roster_info"] = {
    {
        "Agantica", -- [1]
        "ROGUE", -- [2]
        "09/03-2013", -- [3]
    }, -- [1]
    {
        "Intikamim", -- [1]
        "PALADIN", -- [2]
        "17/02-2013", -- [3]
    }, -- [2]
},

成为

"roster_info" : [
    [
        "Agantica",
        "ROGUE",
        "09/03-2013"
    ],
    [
        "Intikamim",
        "PALADIN",
        "17/02-2013"
    ]
]

但是字符串替换将下面的代码片段视为嵌套数组,而它实际上应该是数组中的一个对象:

["bonus_loot_log"] = {
    {
        ["player"] = "Magebox",
        ["timestamp"] = "2013-03-07 13:44:00",
        ["coinsLeft"] = "-1",
        ["reward"] = "|cffa335ee|Hitem:86815:0:0:0:0:0:0:632235520:90:0:445|h[Attenuating Bracers]|h|r",
    }, -- [1]
            {
        ["player"] = "Lîutasila",
        ["coinsLeft"] = "-1",
        ["timestamp"] = "2013-03-07 13:47:00",
    }, -- [2]
},

变成

"bonus_loot_log" : [
    [
        "player" : "Magebox",
        "timestamp" : "2013-03-07 13:44:00",
        "coinsLeft" : "-1",
        "reward" : "|cffa335ee|Hitem:86815:0:0:0:0:0:0:632235520:90:0:445|h[Attenuating Bracers]|h|r"
    ],
    [
        "player": "Lîutasila",
        "coinsLeft": "-1",
        "timestamp": "2013-03-07 13:47:00"
    ]
]

这是一个字符串转换脚本,只能在第一个片段上运行。
lua_string
    .replace(/\[(.*)\]\s\=\s/g,'$1:')     // change equal to colon & remove outer brackets
    .replace(/[\t\r\n]/g,'')              // remove tabs & returns
    .replace(/\}\,\s--\s\[\d+\]\}/g,']]') // replace sets ending with a comment with square brackets
    .replace(/\,\s--\s\[\d+\]/g,',')      // remove close subgroup and comment
    .replace(/,(\}|\])/g,'$1')            // remove trailing comma
    .replace(/\}\,\{/g,'],[')             // replace curly bracket set with square brackets
    .replace(/\{\{/g,'[[')                // change double curlies to square brackets
    .replace(/EPGP_DB\s\=/,'');

所以,我需要帮助让Lua正确转换一个对象数组(第二个示例)。


epgp.lua 是如何生成的?如果是一段 Lua 代码生成了这个输出,你可以编辑该代码并使用 LuaJSON 库/模块。 - hjpotter92
当你退出《魔兽世界》时,这是由插件生成的。你所要做的就是将原始数据文件上传到你的网站上。 - Mottie
这是因为你的 用方括号替换以注释结尾的集合将双花括号改为方括号 代码行。双花括号并不一定意味着数组内嵌数组。在 Lua 中,对象内嵌数组也是双花括号。 - Egor Skriptunoff
@EgorSkriptunoff,您能否更新演示以反映您所描述的内容?我的问题是如何区分数组内的对象和数组本身,或者也许有更好的方法我还没有想到? - Mottie
你可以使用 https://www.npmjs.com/package/luaparse。 - Jason C
3个回答

11

通常情况下,您不能仅使用字符串操作将任何Lua表转换为JSON数据。问题在于,虽然Lua同时用于数组和字典的表,但JSON需要两种不同的类型。还有其他语法差异。

最好通过一个可以在Lua和JSON之间进行转换的模块来解决这个问题。请查看Lua有关JSON模块的维基,找到一个将Lua转换为JSON的Lua模块。有多个模块可供选择,其中一些是纯Lua模块,非常适合嵌入到WoW中。它们可以正确地检测表格是否表示数组或字典,并输出相关的JSON。


3
感谢给予正确方向的推动。如果数据使用者需要JSON格式,但是您手头只有一个Lua表格,正确的做法是从Lua代码中直接生成JSON,而不是尝试进行文本替换,这种方法只有在使用完整的Lua解析器时才能成功。实际上就是让Lua代码首先生成JSON输出,这已经是一个解决了的问题。 - RBerteig

2

通过语法分析可以稳定地进行转换。然而,这是一个非常繁琐的过程。

        $(function () {
            $("#run").on("click", function () {
                let src = $("#src").val();
                let [i, contents] = convert(src, 0, []);

                function isValue(element){
                    let idx = contents.indexOf(element) + 1
                    for(let i=idx; i < contents.length; i++){
                        if(["SPACE","TAB","RETURN"].indexOf(contents[i].type) > -1) continue;
                        if(contents[i].type == "SPLIT") return 0
                        if(contents[i].type == "BRKT_F") return 2
                        if(["BRKT_S","BRKT_W","BREAK","FBREAK"].indexOf(contents[i].type) > -1) return 1
                    }
                }
              

                let converted = "";
                contents.forEach((element, index) => {
                    switch(element.type){
                        case "NUMBER":{
                            converted += element.content
                            break;
                        }
                        case "UNKNOWN": {
                            if(isValue(element)==1){
                              if(element.content == "return"){
                              } else if(["true","false"].indexOf(element.content)>-1){
                                converted += element.content
                              } else {
                                converted += '"' + element.content + '"'
                              }
                            } else if(isValue(element)==2){
                                converted += element.content
                            } else {
                                converted += '"' + element.content + '"'
                            }
                            break;
                        }
                        case "STR_S":
                        case "STR_D":{
                            converted += element.content
                            break;
                        }
                        case "BRKT_S":{
                            converted += element.content
                            break;
                        }
                        case "BRKT_W":{
                            converted += element.content
                            break;
                        }
                        case "BRKT_F":{
                            converted += element.content
                            break;
                        }
                        case "SPACE":{
                            converted += element.content
                            break;
                        }
                        case "TAB":{
                            converted += element.content
                            break;
                        }
                        case "RETURN":{
                            converted += element.content
                            break;
                        }
                        case "BREAK":{
                            converted += ","
                            break;
                        }
                        case "FBREAK":{
                            converted += "."
                            break;
                        }
                        case "SPLIT":{
                            converted += ":"
                            break;
                        }
                    }
                });
                $("#result").val(converted)
            })
        })

      function getBracketSurfaceInner(contents, element){
        if(["BRKT_S", "BRKT_W", "BRKT_F"].indexOf(element.type) == -1 || "]})".indexOf(element.content) == -1) return "";
        let idx = contents.indexOf(element)
        let innerElements = [];
        let nest = 1;
        for(let i=idx-1; i>=1; i--){
          if(["BRKT_S", "BRKT_W", "BRKT_F" ].indexOf(contents[i].type)>=0){
            if("]})".indexOf(contents[i].content)>=0){ nest ++ }
            if("[{(".indexOf(contents[i].content)>=0){ nest -- }
          }
          if(nest==0 && contents[i].type == element.type){
            return innerElements;
          }
          if(nest == 1) {
            innerElements.unshift(contents[i]);
          }
        }
        return innerElements;
      }


      function removeLastCamma(contents, element){
        let idx = contents.indexOf(element)
        let last = -1;
        for(let i=idx-1; i>=1; i--){
          if(["NUMBER", "UNKNOWN", "STR_S", "STR_D"].indexOf(contents[i].type)>=0) return;
          if(contents[i].type == "BREAK"){
              last = i;
              break;
          }
        }
        contents.splice(last, 1);
      }

        function convert(text, pos, contents) {

            let MODE = undefined;
            // NUMBER
            // UNKNOWN
            // SPLIT
            // BREAK
            // FBREAK
            // STR_S
            // STR_D
            // BRKT_S
            // BRKT_W
            // BRKT_F
            // CTRL
            // RETURN
            let MODES = [MODE];

            let content = "", currentElement;

            let i, c

            function PUSH_BEFORE(replace) {
                if (content.length > 1) {
                    contents.push({
                        type: MODE,
                        content: content.slice(0, content.length - 1),
                    });
                }
                content = "" + (replace ? replace : c)
                currentElement = contents[contents.length-1];
                MODE = MODES.shift()
            }

            function PUSH_AFTER(replace) {
                if (content.length > 0) {
                    let str = (replace ? content.slice(0, content.length - 1) + replace : content.slice(0, content.length));
                    contents.push({
                        type: MODE,
                        content: str,
                    });
                }
                content = ""
                currentElement = contents[contents.length-1];
                MODE = MODES.shift()
            }


            for (i = pos; i < text.length; i++) {
                c = text.charAt(i)
                content = content + c

                if (MODE == "ESCAPE") {
                    MODE = MODES.shift()
                } else
                if (MODE == "STR_S") {
                    if (c == "'") {
                        PUSH_AFTER('"')
                    }
                } else
                if (MODE == "STR_D") {
                    if (c == '"') {
                        PUSH_AFTER()
                    }
                } else
                if (MODE == "BRKT_S") {
                    if (c == ']') {
                        PUSH_BEFORE()
                    }
                } else
                if (MODE == "BRKT_F") {
                    if (c == ')') {
                        PUSH_BEFORE()
                    }
                } else
                if (MODE == "BRKT_W") {
                    if (c == '}') {
                        PUSH_BEFORE()
                    }
                } else {

                    switch (c) {
                        case "{":{
                            PUSH_BEFORE()
                            MODE = "BRKT_W"
                            let begin_idx = contents.length; 
                            contents.push({
                                type: MODE,
                                content: c,
                            });
                            MODES.push(MODE)
                            let [f, innerContents] = convert.call(this, text, i + 1, contents)
                            removeLastCamma(contents, contents[contents.length-1]);
                            
                            let surface = getBracketSurfaceInner(innerContents, innerContents[innerContents.length-1]);
                            let d = 0;
                            for(let l=0; l<surface.length; l++){
                                if(surface[l].type == "SPLIT") { d = 1; break; }
                            }
                            i = f
                            content = ""
                            if(d==0){
                                contents[begin_idx].type = "BRKT_S";
                                contents[begin_idx].content = "[";
                                contents[contents.length-1].type = "BRKT_S";
                                contents[contents.length-1].content = "]";
                                MODE = MODES.shift() | "BRKT_S"
                            } else {
                                MODE = MODES.shift() | "BRKT_W"
                            }
                            break;
                        }
                        case "}":{
                            PUSH_BEFORE()
                            contents.push({
                                type: "BRKT_W",
                                content: c,
                            });
                            return [i, contents]
                            break;
                        }
                        case "[": {
                            PUSH_BEFORE()
                            MODE = "BRKT_S"
                            let begin_idx = contents.length;
                            contents.push({
                                type: MODE,
                                content: c,
                            });
                            MODES.push(MODE)
                            let [f, innerContents] = convert.call(this, text, i + 1, contents)
                            removeLastCamma(contents, contents[contents.length-1]);
                          
                            innerContents = getBracketSurfaceInner(contents, contents[contents.length-1]);
                            let d = 0;
                            for(let l=0; l<innerContents.length; l++){
                              if(["BREAK", "BRKT_F"].indexOf(innerContents[l].type)>-1) {d = 1; break; }
                            }
                            if(d==0){
                                contents[begin_idx].type = "NOP";
                                contents[begin_idx].content = "";
                                contents[contents.length-1].type = "NOP";
                                contents[contents.length-1].content = "";
                            }
                          
                            i = f
                            content = ""
                            MODE = MODES.shift() | "BRKT_S"
                            break;
                        }
                        case "]": {
                            PUSH_BEFORE()
                            contents.push({
                                type: "BRKT_S",
                                content: c,
                            });
                            return [i, contents]
                            break;
                        }
                        case "(": {
                            PUSH_BEFORE()
                            MODE = "BRKT_F"
                            let begin_idx = contents.length;
                            contents.push({
                                type: MODE,
                                content: c,
                            });
                            MODES.push(MODE)
                            let [f, innerContents] = convert.call(this, text, i + 1, contents)
                            removeLastCamma(contents, contents[contents.length-1]);
                          
                            innerContents = getBracketSurfaceInner(contents, contents[contents.length-1]);

                            contents[begin_idx].type = "BRKT_F";
                            contents[begin_idx].content = "(";
                            contents[contents.length-1].type = "BRKT_F";
                            contents[contents.length-1].content = ")";
                          
                            i = f
                            content = ""
                            MODE = MODES.shift() | "BRKT_F"
                            break;
                        }
                        case ")": {
                            PUSH_BEFORE()
                            contents.push({
                                type: "BRKT_F",
                                content: c,
                            });
                            return [i, contents]
                            break;
                        }
                        case "'": {
                            if(MODE=="STR_D") {
                              break;
                            }
                            PUSH_BEFORE('"')
                            MODE = "STR_S"
                            break;
                        }
                        case '"': {
                            if(MODE=="STR_S") {
                              break;
                            }
                            PUSH_BEFORE()
                            MODE = "STR_D"
                            break;
                        }
                        case "\\": {
                            MODES.push(MODE)
                            MODE = "ESCAPE"
                            break;
                        }
                        case ",": {
                            PUSH_BEFORE()
                            MODE = "BREAK"
                            break;
                        }
                        case ".": {
                            PUSH_BEFORE()
                            MODE = "FBREAK"
                            break;
                        }
                        case "=": {
                            PUSH_BEFORE(":")
                            MODE = "SPLIT"
                            break;
                        }
                        case ":": {
                            PUSH_BEFORE()
                            MODE = "SPLIT"
                            break;
                        }
                        case " ": {
                            if (MODE != "SPACE") {
                                PUSH_BEFORE()
                                MODE = "SPACE"
                            }
                            break;
                        }
                        case "\t": {
                            if (MODE != "TAB") {
                                PUSH_BEFORE()
                                MODE = "TAB"
                            }
                            break;
                        }
                        case "\n": {
                            PUSH_BEFORE()
                            MODE = "RETURN"
                            break;
                        }
                        default: {
                            if (" SPACE TAB RETURN BREAK FBREAK SPLIT ".indexOf(" " + MODE + " ") > -1) {
                                PUSH_BEFORE()
                            }

                            if (!isNaN(content)) {
                                MODE = "NUMBER"
                            }
                            else {
                                MODE = "UNKNOWN"
                            }

                            break;
                        }
                    }
                }
            }
            return [i, contents]
        }
#src {
  width: 400px;
  height: 200px;
}
#result {
  width: 400px;
  height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Source:<br><textarea id="src"></textarea>
<button id="run">Convert</button><br>
Result:<br><textarea id="result"></textarea>


1
// convert EPGP_DB from LUA to JSON
var str = document.getElementsByTagName('data')[0].innerHTML;
var diff;
do {  // replace curlies around arrays with square brackets
    diff = str.length;
    str = str.replace(/\{(((\n\t*)\t)\S.*(\2.*)*)\,\s--\s\[\d+\]\3\}/g,'[$1$3]');
    diff = diff - str.length;
} while (diff > 0);
str = str
.replace(/EPGP_DB\s=\s/, '')         // remove variable definition
.replace(/\s--\s\[\d+\](\n)/g, '$1') // remove comment
.replace(/\,(\n\t*\})/g, '$1')       // remove trailing comma
.replace(/\[(.*?)\]\s\=\s/g,'$1:')   // change equal to colon, remove brackets
.replace(/[\t\r\n]/g,'');            // remove tabs & returns
console.log(str);
json = window.JSON.parse(str);
console.log(json);
document.getElementById('result').innerText = json.global.last_version;

+1 很好的答案,但不幸的是它只在Webkit中有效而在Firefox中无效:http://jsfiddle.net/Mottie/MfncJ/4/(使用完整的epgp.lua文件)- 是否可能是因为Firefox不支持匹配捕获组? - Mottie
@Mottie - 这个字符串对于正则表达式操作来说太长了。 - Egor Skriptunoff
@Mottie - 或者是JSON解析时间过长。 - Egor Skriptunoff
@Mottie - 在我看来,我的代码是正确的。问题出在 JavaScript 方面。我不知道该如何解决它。是否有可能将 Lua->JSON 转换器重写为 Lua 而不是 JavaScript? - Egor Skriptunoff
1
我认为这不是解决此问题的正确方法。如果您想将Lua数据转换为JSON,应该使用其中一个可用的Lua模块来完成此操作,而不是依赖正则表达式来实现。 - Charles Sprayberry
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接