使用 JSON 函数
FileMaker Pro 提供若干个文本函数,可让您的定制化 App 解析和修改其他 FileMaker 函数或其他来源的 JSON 数据,如通过 REST API 传输 JSON 格式数据的 Web 服务。
有关 JSON 数据格式的更多信息,请参阅 json.org。
本主题中显示的示例使用来自面包店的 JSON 数据,该面包店通过 REST API 以 JSON 格式向客户端提供其产品列表。以下脚本将当日特价列表作为 $$JSON 变量中的 JSON 数据返回:
设置变量 [ $url ; "https://bakery.example.com/rest/api/products" ]
从 URL 插入 [ 包括对话框: 关闭; 目标: $$JSON ; $url ; 验证 SSL 证书 ; cURL 选项: "--data list=特价" ]
有关 $$JSON 中返回并在这些示例中使用的数据,请参阅JSON 数据示例。
格式化 JSON 数据
JSON 数据不需要元素之间有空格或行结束符。但是,为了使数据在您调试问题时更易读,请使用 JSONFormatElements 函数,该函数会添加制表符和行结束符,如JSON 数据示例中所示。
解析 JSON 数据
使用以下 JSON 函数解析 JSON 数据—即,要获得键、值或您可以进一步处理的全部 JSON 对象或数组:
-
JSONGetElement – 在 JSON 数据中查询元素(对象、数组或值)
-
JSONListKeys – 列出 JSON 数据中的对象名称(键)或数组索引
-
JSONListValues – 列出 JSON 数据中的值
这些函数的第一个参数 json
指定包含要操作的有效 JSON 数据的文本字段、变量或文本表达式。
第二个参数键或索引或路径
指定要操作的 JSON 数据的部分:
-
键 – JSON 对象中键的名称
-
索引 – JSON 数组中元素的索引(第一个元素的索引为 0)
-
路径 – 键名称、数组索引或二者的层级字符串
支持以下两种用于 键或索引或路径
参数的语法:点表示法和括号表示法。
|
表示 |
|
点表示法 | 括号表示法 | |
---|---|---|
|
|
如果是第一个字符,则为根级别(在点表示法中可选) |
|
|
根级别数组的索引 n 处的元素 |
|
|
位于根级别的名为 name 的对象的关键字 |
|
|
名为 nameC 的对象,其为 nameB 和 nameA 的后代 |
|
|
nameA 对象中数组的第一个元素,其在一组嵌套数组中位于第三个级别 |
|
|
数组的最后一个元素 |
|
|
数组最后一个元素之后的位置。在 JSONSetElement函数中使用将元素添加到数组的末尾。 |
点和括号符号的区别在于此,括号表示法不是使用句点 (.
) 来分离关键字名称,而是使用单引号 ('
) 和括号 ([]
) 括住关键字名称。您可以在 键或索引或路径
中使用任一表示法。但如果键名称包含点,则必须使用括号表示法,以便 JSON 解析程序可以正确识别整个键名称。例如,如果 JSON 对象根的键是 "layout.response"
,则 keyOrIndexOrPath
将是 "['layout.response']"
。
以下示例脚本为 JSON 数组中的每个产品创建记录。假设JSON 数据示例存储在 $$JSON 变量中,则脚本使用 JSONListValues 来获取产品数组的内容,将其显示值列表,并且使用 ValueCount 来确定列表中的产品数。每次通过循环创建产品记录时,脚本都会使用 GetValue 获取列表中产品的 JSON 对象,并将字段设置为使用 JSONGetElement 获取的值。由于 JSON 函数会解析传递给它们的整个 JSON 对象,在多次重复的循环内较小的 JSON 对象上使用 JSON 函数可能更有效。
设置变量 [ $产品列表; 值: JSONListValues ( $$JSON ; "面包店.产品" ) ]
设置变量 [ $产品列表 ; 值: ValueCount ( $产品列表 ) ]
设置变量 [ $i; 值: 1 ]
If [ $产品数量t > 0 ]
Loop [ 刷新: 始终 ]
新建记录/请求
设置变量 [ $产品 ; 值: GetValue ( $产品列表 ; $i ) ]
设置字段 [ 产品::ID ; JSONGetElement ( $产品 ; "id" ) ]
设置字段 [ 产品::价格 ; JSONGetElement ( $产品 ; "价格" ) ]
设置字段 [ 产品::现货 ; JSONGetElement ( $产品 ; "现货" ) ]
提交记录/请求 [包括对话框: 关闭]
设置变量 [ $i ; 值: $i + 1 ]
Exit Loop If [ $i > $产品数量 ]
End Loop
End If
更改和添加 JSON 数据元素
要在 JSON 数据中更改值并添加元素,请使用JSONSetElement函数。json
和键或索引或路径
参数在此函数中像解析 JSON 数据所描述的那样运行。如果键或索引或路径
指定现有元素,则该元素的值将被更改;如果元素不存在,则会添加新元素。
JSONSetElement 将指定的元素设置为值
参数。您可以指定从简单字符串或数字到复杂对象或数组的任何有效的 JSON 值。
类型
参数指定值
中的数据类型,以使 JSON 分析器在处理每种数据类型时都将遵循限制规则。有关支持的数据类型,请参阅JSONSetElement函数。要将数据插入到已设置为有效 JSON 元素格式的 json
中,请将类型
设置为 JSONRaw
。
下面的示例将新产品的键值对添加到空的 JSON 对象中。然后,新对象添加到 $$JSON 变量中产品数组的末尾(请参阅 JSON 数据示例)。
设置变量 [ $新产品 ; 值:
JSONSetElement ( "{}" ;
[ "id" ; "FB4" ; JSONString ] ;
[ "名称" ; "香草蛋糕" ; JSONString ] ;
[ "价格" ; 17.5 ; JSONNumber ] ;
[ "现货 ; 12 ; JSONNumber ] ;
[ "类别" ; "面包" ; JSONString ] ;
[ "特价" ; true ; JSONBoolean ]
) ]
设置变量 [ $NextIndex ; 值:
ValueCount (
JSONListKeys ( $$JSON ; "面包店.产品" )
) ]
设置变量 [ $$JSON ; 值:
JSONSetElement (
$$JSON ; "面包店.产品[" & $NextIndex & "]" ; $新产品 ;
JSONObject
) ]
另一个创建 JSON 元素的 JSON 函数是 JSONMakeArray函数。它将值列表转换为 JSON 数组。要接受以不同方式格式化的数据,此函数允许您指定分隔每个值的字符和要使用的 JSON 数据类型。
删除 JSON 数据元素
要删除元素,请使用 JSONDeleteElement 函数。json
和键或索引或路径
参数在此函数中像解析 JSON 数据所描述的那样运行。键或索引或路径
参数必须指定 json
中的现有元素。
以下示例将删除产品数组中“id”键在 $$JSON 变量中的值为“FB3”的元素(请参阅 JSON 数据示例)。
设置变量 [ $产品数量 ; 值:
ValueCount (
JSONListKeys ( $$JSON ; "面包店.产品" )
) ]
设置变量 [ $i ; 值: 0 ]
If [ $产品数量 > 0 ]
Loop [ 刷新:始终 ]
设置变量 [ $ID ; 值:
JSONGetElement ( $$JSON ; "面包店.产品[" & $i & "]id" ) ]
If [ $ID = "FB3" ]
设置变量 [ $$JSON ; 值:
JSONDeleteElement ( $$JSON ; "面包店.产品[" & $i & "]" ) ]
退出脚本 [ 文本结果: 0 ]
End If
设置变量 [ $i ; 值: $i + 1 ]
Exit Loop If [ $i ≥ $产品数量 ]
End Loop
End If
优化 JSON 性能
每当 JSON 函数解析文本输入时,JSON 解析器就会创建二进制(非文本)表示,以加快 JSON 的后期处理速度。有一个自动机制来解析和缓存 JSON,还有一个显式机制:JSONParse函数。
利用自动 JSON 缓存
解析需要时间,因此 JSON 函数在内存中缓存最后解析的 JSON 的二进制表示。这减少了以后再次解析同一 JSON 的需要。只维护一个自动缓存。当解析不同的 JSON 值时,之前缓存的 JSON 值会被丢弃。解析的 JSON 仅存在于内存中 - 在变量、脚本参数和计算中。当用于设置字段值时,它作为文本存储在字段中。
通过行号,此示例说明了最初存储在 $JSON1 和 $JSON2 中的 JSON 文本何时被解析,何时未被解析。
设置变量 [ $id ; JSONGetElement ( $JSON1 ; "id" ) ]
设置变量 [ $name ; JSONGetElement ( $JSON1 ; "名称" ) ]
设置变量 [ $price ; JSONGetElement ( $JSON2 ; "价格" ) ]
设置变量 [ $category ; JSONGetElement ( $JSON1 ; "类别" ) ]
-
$JSON1 被解析,二进制表示保存在缓存中。
-
$JSON1 不需要再次解析。
-
$JSON2 被解析和缓存,替换了之前缓存的值。
-
$JSON1 再次被解析,因为它不再被缓存。
因此,为了利用自动缓存,最好一次使用一个 JSON 值(在上述示例中,交换第 3 行和第 4 行,以避免再次解析 $JSON1)。
使用 JSONParse
要解析并显式存储变量和自动缓存中 JSON 值的二进制表示,请使用 JSONParse函数。函数的 json
参数是评估 JSON 数据的任何文本表达式,无论是在字段或字符串中格式化为 JSON 的文本,还是在变量中已解析的 JSON。
以下示例解析 JSON 文本:
设置变量 [ $JSON1 ; "{ \"产品\": {\"id\": \"FB1\"} }" ]
设置变量 [ $JSON1 ; JSONParse ( $JSON1 ) ]
-
$JSON1 只是文本表示,如下所示:
{ "产品": {"id": "FB1"} }
-
使用 JSONParse 后:
-
自动缓存包含二进制表示。
-
$JSON1 变量包含文本和二进制表示。
-
如果不使用 JSONParse,那么在这个示例中:
设置变量 [ $JSON1 ; "{ \"产品\": {\"id\": \"FB1\"} }" ]
设置变量 [ $JSON2 ; JSONGetElement ( $JSON1 ; "产品") ]
-
和以前一样,$JSON1 只是文本表示。
-
使用 JSONGetElement 后:
-
自动缓存包含二进制表示。
-
$JSON2 包含
{"id": "FB1"}
的二进制表示。 -
$JSON1 仍然只是文本表示。文本已被解析,但二进制表示仅在自动缓存中。
-
与添加 JSONParse 时发生的情况形成对比:
设置变量 [ $JSON1 ; "{ \"产品\": {\"id\": \"FB1\"} }" ]
设置变量 [ $JSON1 ; JSONParse ( $JSON1 ) ]
设置变量 [ $JSON2 ; JSONGetElement ( $JSON1 ; "产品") ]
-
和以前一样,$JSON1 只是文本表示。
-
使用 JSONParse 后:
-
自动缓存包含二进制表示。
-
$JSON1 包含文本和二进制表示。
-
-
使用 JSONGetElement 后:
-
$JSON2 包含
{"id": "FB1"}
的二进制表示。此时,文本表示不存储在 $JSON2 中,而是仅在稍后需要时生成。
-
请注意,JSONGetElement 不需要解析第 3 行中的 $JSON1,因为它可以使用第 2 行中缓存的 $JSON1 的二进制表示。
就像解析文本以获得二进制表示需要时间一样,将二进制 JSON 转换为文本也是如此。这就是 JSONGetElement 和 JSONSetElement 等 JSON 函数的结果只是二进制表示的原因之所在。文本表示仅在需要时创建,例如在字段中存储变量(如 $JSON2)或在数据查看器中查看变量时。
您可以使用配套函数 JSONParsedState 来发现给定的 JSON 值是否解析了与其一起存储的 JSON,JSON 是否有效,以及 JSON 是什么类型。
最佳实践
当反复处理大型 JSON 结构(特别是在循环中)或处理 JSON 数据中的多个元素时,操作顺序可以对性能产生重大影响。
为了充分利用自动 JSON 缓存功能,最好避免这样的操作:先加载一个字段中的 JSON 数据进行处理,接着切换到另一个字段进行处理,之后又返回到第一个字段。每次 JSON 函数处理不同的 JSON 文本时,之前缓存的 JSON 都会被丢弃,因此必须再次解析文本。例如,这会导致性能最慢:
# 示例 - 没有效率
设置变量 [ $namel ; 值: JSONGetElement ( Table::JSON1 ; "名称" ) ]
设置变量 [ $name2 ; 值: JSONGetElement ( Table::JSON2 ; "名称" ) ]
设置变量 [ $id1 ; Value: JSONGetElement ( Table::JSON1 ; "id" ) ]
设置变量 [ $id2 ; Value: JSONGetElement ( Table::JSON2 ; "id" ) ]
只需重新调整步骤顺序,确保在处理其他不同的 JSON 数据之前,先完成对同一份 JSON 数据的所有操作,这样自动缓存机制就能发挥作用,避免对同一字段的数据进行二次解析。
# 示例 - 较佳
设置变量 [ $namel ; 值: JSONGetElement ( Table::JSON1 ; "名称" ) ]
设置变量 [ $id1 ; Value: JSONGetElement ( Table::JSON1 ; "id" ) ]
设置变量 [ $name2 ; Value: JSONGetElement ( Table::JSON2 ; "名称" ) ]
设置变量 [ $id2 ; Value: JSONGetElement ( Table::JSON2 ; "id" ) ]
最好、最灵活的方法是使用 JSONParse 函数从每个源获取 JSON 文本,将其解析并存储在变量中。这样只解析一次。然后,您按什么顺序进一步操作(如获取或设置元素)并不重要,因为这些 JSON 函数可以使用变量,而这些变量包含已解析的二进制表示。
# 示例 - 最佳
设置变量 [ $JSON1 ; Value: JSONParse ( Table::JSON1 ) ]
设置变量 [ $JSON2 ; Value: JSONParse ( Table::JSON2 ) ]
设置变量 [ $namel ; Value: JSONGetElement ( $JSON1 ; "名称" ) ]
设置变量 [ $name2 ; Value: JSONGetElement ( $JSON2 ; "名称" ) ]
设置变量 [ $id1 ; Value: JSONGetElement ( $JSON1 ; "id" ) ]
设置变量 [ $id2 ; Value: JSONGetElement ( $JSON2 ; "id" ) ]
处理 JSON 数据中的错误
如果在解析 json
参数时出错,JSON 函数会返回"?",后面跟着一条来自 JSON 分析器的错误消息。
例如,如果 JSON 数据示例中“面包店”键后面缺少“ :”,则计算
JSONGetElement ( $$JSON ; "面包店.产品[0]id" )
返回下面的错误消息:
? * Line 3, Column 2
Missing ':' after object member name
* Line 13, Column 5
Extra non-whitespace after JSON value.
要在使用 JSON 数据之前确定其是否有效,请使用 JSONFormatElements 函数并测试第一个字符是否为 "?"。例如:
设置变量 [ $结果 ; 值: JSONFormatElements ( $$JSON ) ]
If [ Left ( $结果 ; 1 ) = "?" ]
# $$JSON 包含无效 JSON 数据。End If
或者,要确定 JSON 数据是否有效,以及在使用之前是否具有特定的 JSON 类型,请使用 JSONGetElementType函数。例如,要测试 $$JSON 是否为有效的 JSON 对象:
设置变量 [ $result ; 值: JSONGetElementType( $$JSON, "" ) ]
If [ $结果 ≠ JSONObject ]
# $$JSON 包含无效的 JSON 数据。
End If
另一种检测错误的方法是使用 JSONParsedState函数。它可以告诉您 JSON 数据是否已解析并且有效,如果是,还会告知是什么类型的 JSON 数据。
设置变量 [ $result ; 值: JSONParse ( $$JSON ) ]
设置变量 [ $ParsedState ; 值: JSONParsedState ( $result ) ]
If [ $ParsedState < 0 ]
# $$JSON 已解析,但包含无效的 JSON 数据。
Else If [ $ParsedState = 0 ]
# $$JSON 尚未解析。
Else If [ $ParsedState > 0 ]
# $$JSON 已解析且有效。 $ParsedState 表示 JSON 类型。 设置变量 [ $type ; 值:
Case (
$ParsedState = JSONString ; "JSON type is JSONString" ;
$ParsedState = JSONNumber ; "JSON type is JSONNumber" ;
$ParsedState = JSONObject ; "JSON type is JSONObject" ;
$ParsedState = JSONArray ; "JSON type is JSONArray" ;
$ParsedState = JSONBoolean ; "JSON type is JSONBoolean" ;
$ParsedState = JSONNull ; "JSON type is JSONNull"
)
]
显示自定义对话框 [ $type ]
End If
JSON 数据示例
以下 JSON 数据示例包含一个“面包店”对象,其包含由三个“产品”对象组成的数组,每个对象都有几个键值对。
{
"面包店" :
{
"产品" :
[
{
"id" : "FB1",
"名称" : "面包圈",
"价格": 1.99,
"现货" : 43,
"类别" : "面包",
"特价" : true
},
{
"id" : "FB2",
"价格": 22.5,
"名称" : "巧克力蛋糕",
"现货" : 23,
"类别" : "蛋糕",
"特价" : true
},
{
"id" : "FB3",
"价格": 3.95,
"名称" : "法棍面包",
"现货" : 34,
"类别 : "面包",
"特价" : true
}
]
}
}
注释
-
JSON 分析器保留数组中元素的顺序,但不保留对象中元素的顺序。因此,JSON 函数返回对象中的元素的顺序可能与指定的顺序不同。
-
在 JSON 数据中,分数数值必须使用句点“.”作为小数位分隔符,而与您计算机的系统格式指定的分隔符或者创建 FileMaker Pro 文件时使用的格式无关。