You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DCS:World Lua脚本JSON编码遇unexpected type `function`错误的排查

DCS World Lua脚本JSON编码"unexpected type function"问题排查与解决

问题背景

为DCS:World编写的Lua脚本,用于提取任务数据中的目标信息并发送至Node.js服务器。其他功能正常,但执行local jsonString = self.JSON:encode(message).." \n"时抛出**"unexpected type function"**错误。测试type(message)返回table,但JSON序列化仍失败,需定位根源、排查隐藏的函数类型值并找到最优解决办法。

相关代码

koUDPSocket = {}

koUDPSocket.host = "127.0.0.1"
--koTCPSocket.host = "85.221.224.254"
koUDPSocket.port = 52525
koUDPSocket.socket = socket.udp()
koUDPSocket.JSON = require "JSON"

function koUDPSocket:send(data, typee)
    local message = {
        typee = typee,
        data = data,
    }
    env.info("SENDING:")
    env.info(koEngine.TableSerialization(message))
    local jsonString = self.JSON:encode(message).." \n"

    socket.try(self.socket:sendto(jsonString, self.host, self.port))
    env.info("UDP DATA SENT")
 end

function countUnitsInTable(table)
    local count = 0
    for _, _ in pairs(table) do
        count = count + 1
    end
    return count
end

function koEngine.loadMissionDataShort()
    koEngine.debugText("koEngine.loadMissionDataShort")
    koEngine.debugText("loading shorted mission data")

    for categoryName, categoryTable in pairs(MissionData) do
        if type(categoryTable) == "table" and categoryName ~= "properties" then
            for objectiveName, objectiveTable in pairs(categoryTable) do
                if type(objectiveTable) == "table" then
                    local unitCount = 0
                    if objectiveTable.groups then
                        for _, group in pairs(objectiveTable.groups) do
                            if group.units then
                                unitCount = unitCount + countUnitsInTable(group.units)
                            end
                        end
                    end

                    local shortData = {
                        underAttack = objectiveTable.underAttack,
                        coa = objectiveTable.coa,
                        status = objectiveTable.status,
                        priority = objectiveTable.priority,
                        type = categoryName,
                        units = unitCount
                    }
                    missionDataShort[objectiveName] = shortData
                    koEngine.debugText("insert: " .. tostring(objectiveName))
                end
            end
        end
    end

    koUDPSocket:send(missionDataShort, "MissionDataShort")
    koEngine.debugText("END populating short data")
end

日志片段

2023-08-23 19:27:44.840 INFO    SCRIPTING (Main): SENDING:
2023-08-23 19:27:44.848 INFO    SCRIPTING (Main): {
    ['data'] = {
        ['FARP Oche'] = {
            ['type'] = 'FARP',
            ['underAttack'] = false,
            ['status'] = 'closed',
            ['priority'] = 'strategic',
            ['units'] = 0,
            ['coa'] = 'neutral',
        },
        ['Antenna Bulls'] = {
            ['type'] = 'Communication',
            ['underAttack'] = false,
            ['status'] = 'closed',
            ['priority'] = 'strategic',
            ['units'] = 0,
            ['coa'] = 'neutral',
        },
        ['FARP Tsvirmi'] = {
            ['type'] = 'FARP',
            ['underAttack'] = false,
            ['status'] = 'closed',
            ['priority'] = 'strategic',
            ['units'] = 0,
            ['coa'] = 'neutral',
        },
        ['Sukhumi-Babushara'] = {
            ['type'] = 'Aerodrome',
            ['underAttack'] = false,
            ['status'] = 'open',
            ['priority'] = 'strategic',
            ['units'] = 27,
            ['coa'] = 'blue',
        },
        ['FARP Lentehi'] = {
            ['type'] = 'FARP',
            ['underAttack'] = false,
            ['status'] = 'closed',
            ['priority'] = 'strategic',
            ['units'] = 0,
            ['coa'] = 'neutral',
        },
        ['Antenna Balkariya'] = {
            ['type'] = 'Communication',
            ['underAttack'] = false,
            ['status'] = 'closed',
            ['priority'] = 'strategic',
            ['units'] = 3,
            ['coa'] = 'red',
        },
        ['Antenna Oche'] = {
            ['type'] = 'Communication',
            ['underAttack'] = false,
            ['status'] = 'closed',
            ['priority'] = 'strategic',
            ['units'] = 0,
            ['coa'] = 'neutral',
        },

问题根源

JSON序列化库无法处理函数类型的值。虽然type(message)返回table,但message或其嵌套子表中存在未被koEngine.TableSerialization打印的函数类型字段——可能是原任务数据的元方法(metamethod)、隐藏字段,或是其他脚本添加的函数属性。

排查方法

1. 递归遍历检查所有元素类型

编写递归函数,遍历整个表结构(包括嵌套子表),打印所有键值对的类型,定位隐藏的函数:

function inspectTable(t, depth)
    depth = depth or 0
    local indent = string.rep("  ", depth)
    for k, v in pairs(t) do
        local vType = type(v)
        env.info(string.format("%s%s: %s", indent, tostring(k), vType))
        if vType == "table" then
            inspectTable(v, depth + 1)
        elseif vType == "function" then
            env.info(string.format("%s⚠️  发现函数类型字段: %s", indent, tostring(k)))
        end
    end
end

-- 在send函数中替换原日志输出,添加检查
function koUDPSocket:send(data, typee)
    local message = {
        typee = typee,
        data = data,
    }
    env.info("检查消息表结构:")
    inspectTable(message)
    -- env.info(koEngine.TableSerialization(message)) -- 可暂时注释
    -- ... 后续序列化代码
end

2. 单独测试可疑子表

针对日志中显示的目标表,单独测试序列化,缩小排查范围:

-- 在loadMissionDataShort末尾添加测试
local testTarget = missionDataShort["Antenna Balkariya"]
env.info("测试单个目标表:")
inspectTable(testTarget)

local success, err = pcall(koUDPSocket.JSON.encode, koUDPSocket.JSON, testTarget)
if not success then
    env.info("序列化失败: "..err)
end

3. 检查原表的元方法

DCS的任务数据可能带有元表,其中包含函数类型的元方法,可通过以下代码检查:

-- 检查objectiveTable的元表
local mt = getmetatable(objectiveTable)
if mt then
    env.info("元表内容:")
    inspectTable(mt)
end

最优解决方案

1. 白名单构建+清除元表

显式构建干净的数据表,强制清除元表,避免继承原数据的隐藏字段或元方法:

-- 修改shortData的构建方式
local shortData = setmetatable({
    underAttack = objectiveTable.underAttack,
    coa = objectiveTable.coa,
    status = objectiveTable.status,
    priority = objectiveTable.priority,
    type = categoryName,
    units = unitCount
}, nil) -- 强制清除元表,避免携带原表的元方法
missionDataShort[objectiveName] = shortData

2. 序列化前过滤函数类型

递归过滤掉所有函数类型的字段,确保传入JSON库的表中只有可序列化类型:

function filterFunctions(t)
    local filtered = {}
    for k, v in pairs(t) do
        local vType = type(v)
        if vType ~= "function" then
            filtered[k] = vType == "table" and filterFunctions(v) or v
        end
    end
    return filtered
end

-- 在send函数中使用过滤
function koUDPSocket:send(data, typee)
    local message = {
        typee = typee,
        data = filterFunctions(data),
    }
    local jsonString = self.JSON:encode(message).." \n"
    -- ... 后续发送代码
end

3. 添加错误捕获

使用pcall捕获序列化错误,避免脚本崩溃,同时输出详细错误信息:

function koUDPSocket:send(data, typee)
    local message = {
        typee = typee,
        data = data,
    }
    env.info("SENDING:")
    env.info(koEngine.TableSerialization(message))
    
    local success, result = pcall(function()
        return self.JSON:encode(message).." \n"
    end)
    
    if success then
        socket.try(self.socket:sendto(result, self.host, self.port))
        env.info("UDP DATA SENT")
    else
        env.info("JSON编码错误: "..result)
        inspectTable(message) -- 出错时自动检查表结构
    end
 end

内容的提问来源于stack exchange,提问作者Jan Intelkor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 09:14:55