使用 JSON 保存/加载表

一些开发者看到术语 JSON 就认为它会很复杂。JSON 的文档最初可能看起来令人生畏,但理解 JSON 的基础知识以及如何使用它来保存/加载数据非常有用。

什么是 JSON?

JSON 代表 JavaScript 对象 表示法。它是开发者在 JavaScript 中定义对象为数组(Lua 中的表)的方式,这些对象使用键值对来定义,类似于 Lua。语法略有不同,但 Lua 程序员应该能够理解它。考虑以下数据

   
姓名 克拉克·肯特
昵称 超人
地址 大都会
年龄 32

如果我们在 Lua 中为这些数据创建一个表,它可能如下所示

local hero = {
    name = "Clark Kent",
    nickname = "Superman",
    address = "Metropolis",
    age = 32
}

或者,相同的数据可以用 JSON 表示如下

var hero = {
    "name":"Clark Kent",
    "nickname":"Superman",
    "address":"Metropolis",
    "age":32
};

注意 JSON 和 Lua 代码之间的相似之处。在两者中,我们都声明键值对,其中位于= (Lua): (JSON)的左侧,而位于右侧。一个细微的差别是 JSON 使用字符串作为键,因为该表示法的一个目的是促进序列化。更具体地说,这意味着将内存中的二进制对象转换为可以通过网络传输,然后转换回内存中的东西。因此,文本、数字和布尔值 (true/false) 是可以在 JSON 数据中指定的唯一单个项目  — 您不能包含 Corona 显示对象或任何依赖于用户数据/C 内存的内容。

对于更复杂的表,JSON 还支持在方括号 ([]) 内使用项目数组和在大括号 ({}) 内使用对象。让我们将示例数据扩展到 Lua 数组中的多个英雄

local heroes = {
    { name="Clark Kent", nickname="Superman", address="Metropolis", age=32 },
    { name="Bruce Wayne", nickname="Batman", address="Gotham", age=36 },
    { name="Diana Prince", nickname="Wonder Woman", address="New York", age=28 },
}

或者在 JSON 中…

var heroes = [
    { "name":"Clark Kent", "nickname":"Superman", "address":"Metropolis", "age":32 },
    { "name":"Bruce Wayne", "nickname":"Batman", "address":"Gotham", "age":36 },
    { "name":"Diana Prince", "nickname":"Wonder Woman", "address":"New York", "age":28 },
    ]
};

再次注意,它与 Lua 非常相似  — 这就是 JSON 在 Lua 开发者中流行的原因之一。与 XML 相比,JSON 的语法也更紧凑和轻量级,在 XML 中,除了数据之外,您还需要开始和结束标签。最后,许多您的应用程序可能连接到的在线服务也使用 JSON。

JSON 的应用

作为 Corona 开发者,无需完全理解 JSON 即可使用它,因为我们提供了两个方便的 API 调用,作为内置 JSON 库的一部分。函数 json.encode() 接受一个 Lua 表并将其转换为字符串格式的 JSON(这就是数据被序列化的地方)。相反,在处理格式化的 JSON 数据字符串时,可以使用 json.decode() 将其转换回 Lua 表(这就是数据被反序列化的地方).

使用上面的示例数据,让我们使用 json.encode() 将 Lua 表编码为 JSON,并使用 print() 将其值打印到 Corona 控制台

local json = require( "json" )  -- Include the Corona JSON library

local heroes = {
    { name="Clark Kent", nickname="Superman", address="Metropolis", age=32 },
    { name="Bruce Wayne", nickname="Batman", address="Gotham", age=36 },
    { name="Diana Prince", nickname="Wonder Woman", address="New York", age=28 },
}

local serializedJSON = json.encode( heroes )
print( serializedJSON )

这将输出如下内容

[{"nickname":"Superman","age":32,"name":"Clark Kent","address":"Metropolis"},{"nickname":"Batman","age":36,"name":"Bruce Wayne","address":"Gotham"},{"nickname":"Wonder Woman","age":28,"name":"Diana Prince","address":"New York"}]

现在,要将此 JSON 字符串解码回 Lua 表,只需调用 json.decode() 并使用 print() 将其值打印到控制台。这将输出创建的 Lua 表的典型内存引用,例如table: 0x6080010613c0,并且该数据表可以像任何其他 Lua 表一样使用。

local serializedJSON = json.encode( heroes )
print( serializedJSON )

local newHeroes = json.decode( serializedJSON )
print( newHeroes )

通过这两个步骤,我们将 Lua 表转换为 JSON 字符串,然后将其转换回 Lua 表,甚至无需理解 JSON。JSON 就是这么简单!

将表保存/加载到 JSON

在 Lua 和 JSON 之间编码和解码数据最有用的目的之一是保存和加载数据到/从本地存储。这使您可以完成无数的任务,包括存储游戏设置、读取关卡数据/配置、跟踪会话之间的高分,以及几乎任何其他需要存储合理可预测数量数据的任务。

注意

如果您需要处理数据库格式的大量数据,或者需要根据不同的参数对数据进行排序和过滤,JSON 则不是理想的解决方案。在这种情况下,请参阅使用 SQLite 访问数据库教程,了解有关如何在 SQLite 数据库中保存、加载和利用数据的详细信息。

让我们从创建一个方便的 Lua 模块来包含必要的函数开始。您可以在Corona 中的外部模块教程中进一步学习这个概念,但基本上我们将从一个典型的设置开始

local M = {}

local json = require( "json" )
local defaultLocation = system.DocumentsDirectory

return M

将此文件保存到您的主项目目录中,名为 loadsave.lua。本质上,这段初始代码设置了一个 Lua 表 M,它将存储我们在以下部分中创建的函数,并返回该表,以便这些函数可以被任何require()此模块的模块访问。此外,由于我们显然将使用 JSON,因此我们包含 Corona JSON 库并设置默认持久位置 (system.DocumentsDirectory) 以将 JSON 文件保存/加载到/从该位置。

保存函数

现在让我们向 loadsave.lua 模块添加一个保存函数

local M = {}

local json = require( "json" )
local defaultLocation = system.DocumentsDirectory


function M.saveTable( t, filename, location )

    local loc = location
    if not location then
        loc = defaultLocation
    end

    -- Path for the file to write
    local path = system.pathForFile( filename, loc )

    -- Open the file handle
    local file, errorString = io.open( path, "w" )

    if not file then
        -- Error occurred; output the cause
        print( "File error: " .. errorString )
        return false
    else
        -- Write encoded JSON data to file
        file:write( json.encode( t ) )
        -- Close the file handle
        io.close( file )
        return true
    end
end

return M

此函数相对简单,它遵循读取和写入文件指南中的基本示例。它的工作原理如下

  • 此函数接受三个参数:tfilenamelocation  — t 是应转换为 JSON 并保存的 Lua 表,filename 是目标 JSON 文件的名称,location 是保存文件的本地存储位置。

  • 在函数内部,我们首先设置本地存储位置。如果未定义 location 参数,我们将其设置为 defaultLocation (system.DocumentsDirectory),这是 99% 的情况下应该保存文件的位置。

  • 接下来,我们根据 filename 参数和位置创建目标文件的路径。

  • 之后,我们以写入访问权限 ("w") 打开文件句柄,因为我们正在保存(写入)文件。

  • 最后,假设文件句柄有效,我们将JSON 编码的(json.encode( t ))写入目标文件路径,关闭文件句柄,并返回 true

加载函数

如果没有将数据加载回内存的方法,保存数据就没有多大用处,所以让我们向 loadsave.lua 模块添加一个加载函数

function M.loadTable( filename, location )

    local loc = location
    if not location then
        loc = defaultLocation
    end

    -- Path for the file to read
    local path = system.pathForFile( filename, loc )

    -- Open the file handle
    local file, errorString = io.open( path, "r" )

    if not file then
        -- Error occurred; output the cause
        print( "File error: " .. errorString )
    else
        -- Read data from file
        local contents = file:read( "*a" )
        -- Decode JSON data into Lua table
        local t = json.decode( contents )
        -- Close the file handle
        io.close( file )
        -- Return table
        return t
    end
end

return M

此函数也很基本

  • 这次只需要两个参数:filenamelocation — filename 是要读取的 JSON 文件的名称,location 是其本地存储位置。

  • 在函数内部,我们首先确定本地存储位置。与 saveTable() 函数一样,除非 location 参数另有指定,否则将其设置为默认值 system.DocumentsDirectory

  • 接下来,我们根据 filename 参数和位置创建文件的路径。

  • 之后,我们以读取访问权限 ("r") 打开文件句柄,因为我们正在加载(读取)文件。

  • 最后,假设文件句柄有效,我们将文件数据读入 contents 字符串,将其解码回 Lua 表(json.decode( contents )),关闭文件句柄,并返回 t,以便在调用此函数时返回 Lua 表。

使用函数

作为一个非常简单的示例,假设我们有一个包含各种游戏设置的表。使用此表,我们可以使用 saveTable() 函数保存数据。这会将 Lua 表编码为 JSON 并将其保存到 system.DocumentsDirectory()(默认)作为 settings.json

local loadsave = require( "loadsave" )

local gameSettings = {
    musicOn = true,
    soundOn = true,
    difficulty = "easy",
    highScore = 10000,
    highestLevel = 7
}

loadsave.saveTable( gameSettings, "settings.json" )

然后,要重新加载数据,我们可以使用 loadTable() 函数,请求刚刚保存的文件

local loadedSettings = loadsave.loadTable( "settings.json" )

如果您经常使用多个键/值保存和加载数据,那么表输出函数对于检查加载表中究竟包含什么内容可能非常有用。网上有几个关于这个概念的例子,但在输出表内容教程中可以找到一个经过验证且功能强大的版本。

总结

如本教程所示,JSON 提供了一种轻量级的方法,可以将 Lua 表转换为序列化文本字符串,这些字符串可以在在线服务之间交换或保存到本地文件。然后,只需一行代码,即可将它们恢复为原始 Lua 格式。