资讯动态

Lua与JSON互转实战:游戏开发中的类型映射与性能优化

发布时间:2026/10/17 1:28:17 来源:尧图企业网站定制
1. 为什么游戏项目里总绕不开Lua与JSON的互转做过游戏客户端或者服务端逻辑的朋友大概率都遇到过这样的场景策划配了一张技能表用Excel导出成JSON程序在运行时需要把它读进Lua的table里做数值计算或者反过来战斗结算模块用Lua算完一堆结果需要序列化成JSON发给后端做战绩存档。这两个数据结构——Lua的table和JSON的对象/数组——看起来长得挺像但真动手转的时候坑一个接一个。Lua的table是这个语言里唯一的数据结构它既能当数组用又能当字典用还能混着用。JSON呢对象必须是字符串键数组必须是连续整数索引而且值类型只有那么几种。两者之间的映射关系不是一一对应的这就导致互转的时候会出现各种边界情况。比如一个空的table它到底是空对象{}还是空数组[]Lua里nil和false在条件判断里都是假值但JSON里只有null和false这又怎么处理这篇文章主要面向有一定Lua基础、正在做游戏开发或者工具链开发的读者。不管你是刚接触Lua转JSON的新手还是已经用过几个库但总被某些奇怪bug困扰的老手我都会从实际项目经验出发把Lua和JSON互转这件事拆开揉碎讲清楚。包括常用库的选型对比、核心转换逻辑的手写实现、游戏场景下的特殊处理技巧以及那些只有踩过才知道的坑。读完你至少能搞清楚什么时候该用现成库什么时候自己写更靠谱以及怎么避免数据在转换过程中悄悄变形。2. Lua table和JSON数据模型之间的映射关系到底有多复杂2.1 两种数据结构的本质差异Lua的table本质上是一个哈希表键可以是任意类型除了nil值也可以是任意类型。当你写local t {1, 2, 3}的时候Lua会自动给这些值分配整数键1、2、3。当你写local t {name abc, level 10}的时候键就是字符串。更复杂的是你可以写local t {1, 2, 3, name abc}这时候table里既有数组部分又有哈希部分。JSON的数据模型就严格多了。一个JSON值只能是对象、数组、字符串、数字、布尔值或者null。对象是一组无序的键值对键必须是字符串数组是一组有序的值索引从0开始且必须连续。JSON没有混合这个概念你要么是对象要么是数组不能既是对象又是数组。这种本质差异导致了一个核心问题Lua table到JSON的转换不是双射。也就是说一个Lua table转成JSON再转回来得到的table可能和原来不完全一样。最典型的就是空table{}转成JSON可以是{}也可以是[]转回来的时候你没法区分它原本是什么。2.2 类型映射的细节对照先把基本的类型映射关系理清楚这是后面所有讨论的基础。Lua类型JSON类型说明nilnull但table里的nil值在序列化时通常被忽略booleantrue/false直接对应numbernumberLua的number都是双精度浮点JSON不区分整数和浮点stringstring需要注意转义字符的处理table数组array键为连续整数1到ntable对象object键为字符串table混合无法直接表示需要业务层决定策略function/userdata/thread无法表示序列化时通常报错或忽略这里有几个容易出问题的地方。第一Lua的number在JSON里没有整数和浮点的区分但很多游戏后端是用强类型语言写的它们期望收到整数的时候你给个1.0就可能出问题。第二Lua的字符串可以包含任意字节但JSON字符串必须是合法的UTF-8如果游戏里有二进制数据混在字符串里序列化就会炸。第三table里的nil值在Lua里表示不存在但JSON的null是一个明确的值这两者语义不同。2.3 数组和对象的判定难题这是Lua转JSON最核心的难点。给定一个table怎么判断它应该被序列化成JSON数组还是JSON对象常见的判定策略有这么几种只看键的类型如果所有键都是正整数就当数组如果有字符串键就当对象。但{[1]1, [3]3}这种不连续的整数键怎么办按数组处理会丢失信息按对象处理又不符合直觉。看是否有[1]这个键有就当数组没有就当对象。这个策略简单粗暴但{[1]a, nameb}这种混合table就会被当成数组name字段直接丢了。看#操作符的结果如果#t 0就当数组。但#操作符对不连续数组的行为是未定义的不同Lua版本可能返回不同结果。显式标记在table里加一个元字段比如__jsontype array来强制指定。这个最可靠但需要业务代码配合。我在实际项目里最推荐的是组合策略先检查是否有显式的类型标记没有的话再看键的分布。如果所有键都是正整数且从1开始连续就当数组如果所有键都是字符串就当对象混合情况就报错或者按业务规则处理。千万不要用#操作符做唯一判据那个坑太深了。3. 手写一个靠谱的Lua-JSON互转模块需要处理哪些细节3.1 序列化从Lua table到JSON字符串先讲序列化也就是把Lua的table变成JSON字符串。这个过程的核心是递归遍历table根据值的类型拼接字符串。先看一个最基础的实现框架local function serialize(value, indent, currentIndent) local t type(value) if t nil then return null elseif t boolean then return value and true or false elseif t number then -- 处理整数和浮点的格式化 if value math.floor(value) and value ~ math.huge and value ~ -math.huge then return string.format(%d, value) else return string.format(%.14g, value) end elseif t string then return serializeString(value) elseif t table then return serializeTable(value, indent, currentIndent) else error(cannot serialize type: .. t) end end这段代码看起来简单但每个分支都有讲究。number的处理尤其关键我见过太多项目因为浮点数格式化问题导致前后端数据对不上。%.14g这个格式是我试过比较稳的它能保证双精度浮点数的精度不丢失同时不会输出一长串无意义的尾数。如果你确定某个字段是整数最好在业务层就转成整数再传进来不要依赖序列化库去猜。字符串的转义是另一个重灾区。JSON标准要求转义双引号、反斜杠、控制字符以及一些特殊字符。下面这个转义表是我在实际项目中反复验证过的local escapeMap { [] \\, [\\] \\\\, [\b] \\b, [\f] \\f, [\n] \\n, [\r] \\r, [\t] \\t, } local function serializeString(s) -- 先处理必须转义的字符 s s:gsub([%z\1-\31\\], function(c) return escapeMap[c] or string.format(\\u%04x, c:byte()) end) return .. s .. end注意%z在Lua 5.2以后被移除了如果你用的是新版本Lua需要把%z换成\0。这个细节很多网上抄来的代码都没更新直接拿来用会在新版本上出问题。table的序列化是最复杂的部分。需要先判断是数组还是对象然后分别处理。数组的话按顺序遍历对象的话遍历所有键值对。这里有个性能优化点如果table很大用table.concat拼接结果比用..运算符快很多因为Lua的字符串拼接会不断创建新字符串。local function serializeTable(t, indent, currentIndent) local isArray true local maxIndex 0 local count 0 for k, v in pairs(t) do count count 1 if type(k) number and k 0 and k math.floor(k) then if k maxIndex then maxIndex k end else isArray false end end -- 如果所有键都是正整数且连续当数组处理 if isArray and maxIndex count then -- 序列化成数组 local parts {} for i 1, maxIndex do parts[i] serialize(t[i], indent, currentIndent .. indent) end if #parts 0 then return [] end if indent then return [\n .. currentIndent .. indent .. table.concat(parts, ,\n .. currentIndent .. indent) .. \n .. currentIndent .. ] else return [ .. table.concat(parts, ,) .. ] end else -- 序列化成对象 local parts {} for k, v in pairs(t) do if type(k) string then parts[#parts 1] serializeString(k) .. : .. serialize(v, indent, currentIndent .. indent) end end if #parts 0 then return {} end if indent then return {\n .. currentIndent .. indent .. table.concat(parts, ,\n .. currentIndent .. indent) .. \n .. currentIndent .. } else return { .. table.concat(parts, ,) .. } end end end这段代码里有个细节值得注意maxIndex count这个判断。它确保了数组是连续的没有空洞。如果table是{[1]a, [3]c}count是2但maxIndex是3不相等就会走对象分支。但走对象分支的时候数字键会被忽略掉因为对象分支只处理字符串键。这就丢数据了。所以更严谨的做法是在对象分支里也处理数字键把数字键转成字符串键。不过这样转出来的JSON再转回Lua就变成字符串键了语义变了。我的建议是在业务层保证传给序列化函数的table要么是纯数组要么是纯对象混合table直接报错这样能避免很多隐蔽的bug。3.2 反序列化从JSON字符串到Lua table反序列化就是把JSON字符串解析成Lua的table。这个过程比序列化要复杂因为需要处理语法解析、错误恢复、大数字精度等问题。一个完整的JSON解析器需要处理空白字符跳过、字符串解析含转义、数字解析、true/false/null识别、对象和数组的递归解析。手写一个完整的解析器大概需要两三百行代码这里我重点讲几个容易出错的点。数字解析是第一个坑。JSON的数字可以是整数、浮点数、科学计数法。Lua的tonumber函数能处理大部分情况但对于超大整数超过2^53会丢失精度。游戏里如果用到64位ID这个问题就很致命。解决方案是在解析到数字时如果发现是纯数字且长度超过15位就保留为字符串由业务层决定怎么处理。local function parseNumber(s) -- 如果超过15位纯数字保留为字符串避免精度丢失 if s:match(^%-?%d$) and #s 15 then return s end return tonumber(s) end字符串解析的转义处理也要小心。\uXXXX格式的Unicode转义需要正确转换成UTF-8字节序列。很多简易实现直接忽略了这个导致中文或者特殊字符解析出错。local function parseUnicode(s, i) local hex s:sub(i, i 3) local code tonumber(hex, 16) -- 处理代理对surrogate pair if code 0xD800 and code 0xDBFF then -- 需要读取下一个\uXXXX local hex2 s:sub(i 6, i 9) local code2 tonumber(hex2, 16) code 0x10000 (code - 0xD800) * 0x400 (code2 - 0xDC00) i i 6 end -- 转换成UTF-8 if code 0x80 then return string.char(code), i 4 elseif code 0x800 then return string.char( 0xC0 math.floor(code / 0x40), 0x80 code % 0x40 ), i 4 elseif code 0x10000 then return string.char( 0xE0 math.floor(code / 0x1000), 0x80 math.floor(code / 0x40) % 0x40, 0x80 code % 0x40 ), i 4 else return string.char( 0xF0 math.floor(code / 0x40000), 0x80 math.floor(code / 0x1000) % 0x40, 0x80 math.floor(code / 0x40) % 0x40, 0x80 code % 0x40 ), i 4 end end空数组和空对象的解析也有讲究。JSON的[]解析成Lua的空table{}也解析成空table两者在Lua里看起来一样。如果你需要区分就得在解析时加元信息比如给空数组加个__isArray的标记。但大多数游戏场景下不需要区分直接给空table就行。3.3 循环引用的检测与处理Lua的table可以引用自己形成循环引用。比如local t {}; t.self t。这种table直接序列化会导致无限递归最后栈溢出。处理循环引用有两种策略检测到就报错或者用引用标记代替。游戏配置数据一般不会有循环引用但运行时产生的数据有可能。我的做法是在序列化时维护一个已访问table的集合如果发现当前table已经在集合里就抛出明确的错误信息告诉调用方哪个字段导致了循环。local function serialize(value, indent, currentIndent, visited) visited visited or {} if type(value) table then if visited[value] then error(circular reference detected) end visited[value] true local result serializeTable(value, indent, currentIndent, visited) visited[value] nil return result end -- ... 其他类型处理 end注意这里在递归返回后要把visited[value]置回nil因为同一个table可能在兄弟节点中被多次引用非循环的共享引用这种情况是合法的不应该报错。4. 游戏开发中那些必须特殊处理的转换场景4.1 配置表数据的批量转换游戏策划配的数值表通常是从Excel导出的格式规整但字段类型五花八门。我见过一张技能表里同时有整数、浮点数、字符串、布尔值还有用逗号分隔的数组字符串。这种数据转JSON的时候最大的问题是类型推断。比如策划填了一个100这到底是字符串还是数字如果Excel单元格格式是文本导出来就是字符串如果是数字格式导出来就是数字。但策划经常混着填导致同一列里有的行是数字有的行是字符串。程序读进来做计算的时候字符串100和数字100在Lua里用比较是不相等的100 1会报错。我的处理方案是在配置加载层做一次类型规范化。根据配置表的schema定义明确每个字段应该是什么类型加载时强制转换。比如定义level字段是整数那不管JSON里是100还是100都转成数字100。这个转换逻辑放在JSON解析之后、业务使用之前。local function normalizeConfig(config, schema) for key, expectedType in pairs(schema) do local value config[key] if value ~ nil then if expectedType number then config[key] tonumber(value) or 0 elseif expectedType string then config[key] tostring(value) elseif expectedType boolean then config[key] (value true or value true or value 1) end end end return config end这个方案看起来笨但实际项目里非常有效。schema定义可以跟配置表一起维护策划改字段类型的时候程序能立刻发现不匹配。4.2 战斗回放数据的序列化战斗回放是游戏里对序列化要求最高的场景之一。一局战斗可能产生几千条操作记录每条记录包含时间戳、操作类型、参数列表。这些数据需要序列化成JSON存下来回放的时候再解析出来逐条执行。这个场景的特殊要求是体积要小和解析要快。JSON本身是文本格式体积比二进制大不少。如果一局战斗的回放数据有几百KB玩家上传下载都会很慢。我的优化策略是字段名用短名比如t代替timestampa代替action。虽然可读性差了但体积能小30%以上。浮点数保留必要精度就行不要用默认的14位有效数字。战斗回放里的坐标精确到小数点后两位足够了。如果数据量特别大考虑分段序列化每100条记录打成一个JSON数组而不是把所有记录塞进一个大数组。解析速度方面手写的JSON解析器通常比通用库快因为可以针对特定格式做优化。比如战斗回放的JSON结构是固定的解析器可以跳过通用的类型判断直接按预期结构读取。4.3 前后端通信中的空值语义游戏客户端和服务端通信的时候nil、null、空字符串、空table这几个概念的语义经常被混淆。Lua里nil表示不存在JSON里null表示存在但值为空。当服务端发来{name: null}解析成Lua应该是{name nil}还是{name null}如果转成nil那这个键在table里就不存在了和{}没区别。如果转成字符串null又不符合类型预期。我的做法是在通信协议层明确约定JSON的null解析成Lua的nil但保留键的存在性。具体实现是解析到null值时不直接赋值nil而是赋一个特殊的哨兵值业务层判断这个哨兵值来决定怎么处理。或者更简单粗暴协议层约定不使用null需要表示空值就用空字符串或0代替。这个约定虽然不够优雅但能避免大量边界问题。空table的问题前面提过{}和[]在Lua里都是空table。通信协议里如果某个字段可能是空数组也可能是空对象接收方就没法区分。解决方案是在协议文档里明确每个字段的类型接收方按预期类型处理。如果预期是数组但收到空table就当空数组用。5. 现成库和手写方案的选型对比与实战建议5.1 常见Lua JSON库的特点Lua生态里JSON库不少各有侧重。我列几个实际项目里用过的说说各自的适用场景。库名特点适用场景注意事项纯Lua轻量库代码量小无依赖配置表加载、简单数据转换大数字精度可能丢失带C扩展的库解析速度快高频通信、大数据量需要编译跨平台麻烦游戏引擎内置与引擎深度集成引擎内数据流转行为可能被引擎定制过纯Lua库的优点是跨平台无痛任何能跑Lua的地方都能用。缺点是解析大JSON的时候性能一般因为Lua的字符串操作和table操作都是解释执行的。我实测过一个200KB的JSON文件纯Lua库解析大概需要几十毫秒C扩展库只要几毫秒。如果游戏每帧都要解析JSON那必须用C扩展。游戏引擎内置的JSON模块通常做了针对性优化比如支持直接从引擎的资源系统读取或者与引擎的序列化系统打通。但这类模块的行为可能和标准JSON有差异比如对空table的处理、对数字格式化的处理用之前一定要看文档或者读源码确认。5.2 什么时候该自己写现成库虽然方便但以下几种情况我建议自己写或者至少自己封装一层需要严格控制输出格式比如后端要求JSON的键必须按字母序排列或者浮点数必须保留两位小数。通用库通常不提供这种控制。需要处理特殊类型游戏里可能有自定义的数据类型比如向量、颜色、时间戳对象通用库不知道怎么序列化它们。性能要求极高通用库为了兼容各种情况做了很多判断如果数据结构固定可以写一个专用序列化器速度能快好几倍。需要详细的错误信息通用库报错通常只说解析失败不告诉你具体哪个位置、什么原因。自己写可以给出精确的错误定位。自己写不等于从零造轮子。我的做法是基于一个轻量库做二次封装保留它的解析能力替换掉序列化部分加上自己的类型处理和格式控制。这样既省事又灵活。5.3 性能优化的几个实用技巧如果JSON转换成了性能瓶颈可以试试这几个优化手段。缓存序列化结果。如果某个table的内容不变序列化结果可以缓存起来下次直接用。游戏里的配置表数据加载后就固定了序列化一次存成字符串需要的时候直接取。延迟解析。如果JSON数据很大但每次只用其中一小部分可以做成懒加载。解析的时候只记录位置真正访问某个字段时才解析那部分。这个实现起来比较复杂但收益很大。用table池。频繁创建和销毁table会给GC造成压力。可以维护一个table池解析时从池里取用完还回去。注意还回去之前要清空所有字段避免数据残留。避免不必要的字符串拼接。序列化时用table.concat而不是..解析时用string.find定位而不是逐字符遍历。这些细节在数据量大的时候影响很明显。6. 那些年我踩过的转换坑和排查思路6.1 浮点数精度丢失的完整排查过程有一次项目里出现了一个诡异bug客户端显示的金币数量和服务端记录的对不上差了1。排查了半天最后定位到JSON序列化。服务端用强类型语言金币是64位整数。发给客户端的时候序列化成JSON客户端用Lua解析。Lua的number是双精度浮点能精确表示的整数上限是2^53大概是9后面15个零。金币数量如果超过这个值解析出来就会丢失精度。排查过程是这样的先确认服务端发的原始值是多少然后在客户端打印解析后的值发现确实变了。再检查JSON字符串本身发现字符串里的数字是对的问题出在解析环节。最后确认是Lua的number精度限制。解决方案有两个一是金币数量控制在2^53以内这个对大多数游戏来说够用了二是把金币作为字符串传输客户端需要计算的时候用专门的大整数库。我们选了方案一因为改起来简单而且2^53的金币数量在游戏生命周期内不可能达到。这个坑的教训是任何可能超过2^53的数值在Lua里都要小心处理。不光是金币还有经验值、伤害值、时间戳毫秒级时间戳已经接近这个量级了。6.2 中文字符转义导致的乱码问题另一个印象深刻的bug是聊天功能里的中文乱码。玩家发的消息经过JSON序列化再解析偶尔会出现乱码。排查发现是转义处理的问题。我们的序列化代码在处理非ASCII字符时有的字符转成了\uXXXX格式有的直接保留了原始字节。解析的时候\uXXXX格式能正确还原但原始字节如果被错误地当成Latin-1处理就会乱码。根本原因是Lua的字符串是字节序列不包含编码信息。string.sub按字节截取如果截到了多字节UTF-8字符的中间就会产生非法字节序列。JSON标准要求字符串是合法的UTF-8非法字节序列会导致解析失败或者乱码。修复方案是在序列化时统一把所有非ASCII字符转成\uXXXX格式解析时再统一还原。虽然这样会让JSON体积变大一个中文字符变成6个字节的转义序列但能保证正确性。如果对体积有要求可以只在检测到非法UTF-8时才转义合法UTF-8直接保留。6.3 空table判定引发的逻辑错误还有一个坑是空table的判定。游戏里有个功能是检查玩家是否有未领取的奖励逻辑是遍历奖励列表如果列表为空就显示暂无奖励。奖励列表从服务端获取JSON格式是[]。解析成Lua后是空table{}。判断#rewards 0的时候对于空table#返回0逻辑正确。但有一次服务端返回的是{}空对象解析后也是空table#同样返回0逻辑也正确。问题出在另一个地方有个判断是if rewards then空table在Lua里是真值所以会进入有奖励的分支然后遍历的时候又什么都没有导致界面显示异常。这个bug的根源是空table在Lua里既是空又是存在。if rewards then判断的是存在性空table存在所以为真。要判断是否为空必须用next(rewards) nil或者#rewards 0。修复方案是在业务层统一封装一个isEmpty函数同时检查table是否为nil以及是否没有元素。所有需要判断空的地方都用这个函数避免直接依赖Lua的真值判断。local function isEmpty(t) return t nil or next(t) nil end这个函数看起来简单但能避免很多因为Lua真值语义导致的逻辑错误。特别是从JSON解析出来的数据空数组和空对象都是空table用isEmpty判断最稳妥。6.4 大JSON文件解析导致的内存峰值最后一个坑是内存问题。游戏加载一张大型配置表JSON文件有5MB解析过程中内存峰值到了50MB在低端机上直接闪退。分析发现是解析器的实现问题。那个解析器每解析一个值就创建一个新table而且中间结果没有及时释放。5MB的JSON大概有几十万个值每个值都创建table内存自然就上去了。优化方案是改用流式解析边读边处理不保留完整的中间结构。对于配置表这种结构固定的数据可以预分配table避免频繁创建销毁。另外解析完成后主动调用collectgarbage触发一次GC把解析过程中的临时对象回收掉。优化后内存峰值降到了15MB左右低端机也能正常运行了。这个经验告诉我JSON解析的内存开销不只是最终数据的大小中间过程可能放大好几倍。在内存受限的环境下解析器的实现方式比JSON本身的大小更重要。7. 一套可复用的转换模块该怎么组织7.1 模块的接口设计基于上面的经验我整理了一套自己常用的转换模块接口。核心原则是序列化和反序列化分开配置和运行时数据分开错误处理明确。local json {} -- 基础序列化返回JSON字符串 -- param value 要序列化的值 -- param options 可选配置indent缩进、sortKeys键排序、maxDepth最大深度 function json.encode(value, options) end -- 基础反序列化返回Lua值 -- param str JSON字符串 -- param options 可选配置maxDepth最大深度、preserveNull是否保留null function json.decode(str, options) end -- 配置表专用序列化针对游戏配置优化 function json.encodeConfig(config) end -- 配置表专用反序列化带schema校验 function json.decodeConfig(str, schema) end return jsonencode和decode是通用接口处理标准JSON。encodeConfig和decodeConfig是配置表专用做了针对性的优化和校验。这样分开的好处是通用接口保持简洁专用接口可以加各种业务逻辑而不影响通用场景。7.2 错误处理与日志JSON转换的错误处理很重要因为出问题的时候往往很难定位。我的做法是在模块里内置一个错误收集器记录出错的位置、原因、原始数据片段。local function decodeError(str, pos, reason) local contextStart math.max(1, pos - 20) local contextEnd math.min(#str, pos 20) local context str:sub(contextStart, contextEnd) error(string.format( JSON decode error at position %d: %s\nContext: ...%s..., pos, reason, context )) end这样报错的时候能直接看到出错位置附近的原始内容排查起来快很多。另外建议在模块里加一个调试开关打开后打印每次转换的耗时和数据大小方便性能分析。7.3 单元测试的覆盖要点JSON转换模块的单元测试要覆盖这些场景基本类型nil、boolean、number整数/浮点/负数/科学计数法、string空串/中文/特殊字符空值空table、空数组、空对象、null嵌套多层嵌套的table、数组套对象、对象套数组边界超大数字、超长字符串、深层嵌套错误非法JSON、循环引用、不支持的类型每个测试用例都要验证序列化和反序列化的双向一致性。特别是那些转过去再转回来可能变形的场景要明确预期行为并写成测试。-- 测试用例示例 local function testEmptyTable() local t {} local encoded json.encode(t) -- 空table默认序列化成空对象 assert(encoded {}, empty table should encode to {}) local decoded json.decode(encoded) assert(type(decoded) table, decoded should be table) assert(next(decoded) nil, decoded should be empty) end这套测试跑下来基本能覆盖90%以上的实际使用场景。剩下的10%是业务特定的边界情况需要在业务层单独测试。8. 从转换问题反推数据协议设计的改进方向折腾了这么多转换问题之后我最大的体会是很多转换的坑其实可以在数据协议设计阶段就避免。比如空table的歧义问题如果协议里明确规定数组字段永远返回数组即使为空对象字段永远返回对象即使为空那接收方就能按预期类型处理不需要猜。再比如大数字精度问题如果协议里规定所有ID类字段用字符串传输那就完全绕开了Lua的number精度限制。我现在设计游戏数据协议的时候会遵循几条原则类型明确每个字段的类型在协议文档里写清楚不用依赖运行时推断。避免歧义不用null表示空用空字符串或0代替不用空table表示无数据用明确的布尔字段标记。数值安全可能超过2^53的数值一律用字符串传输。扁平优先能扁平的结构不要嵌套减少递归解析的复杂度和出错概率。这些原则看起来是约束实际上省的是后面调试的时间。我算过一笔账在协议设计阶段多花一小时明确类型能省掉后面至少十小时的排查和修复。这个投入产出比非常划算。Lua和JSON的互转本身不复杂复杂的是游戏场景下的各种边界情况和性能要求。把上面这些细节都考虑到写出来的转换模块才能在实际项目里稳定运行。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑