使用 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]"

"[n]"

根级别数组的索引 n 处的元素

".name"

"['name']"

位于根级别的名为 name 的对象的关键字

".nameA.nameB.nameC"

"['nameA']['nameB']['nameC']"

名为 nameC 的对象,其为 nameBnameA 的后代

".[3][2][1]nameA[0]"

"[3][2][1]['nameA'][0]"

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 ; "类别" ) ]
  1. $JSON1 被解析,二进制表示保存在缓存中。

  2. $JSON1 不需要再次解析。

  3. $JSON2 被解析和缓存,替换了之前缓存的值。

  4. $JSON1 再次被解析,因为它不再被缓存。

因此,为了利用自动缓存,最好一次使用一个 JSON 值(在上述示例中,交换第 3 行和第 4 行,以避免再次解析 $JSON1)。

使用 JSONParse

要解析并显式存储变量和自动缓存中 JSON 值的二进制表示,请使用 JSONParse函数。函数的 json 参数是评估 JSON 数据的任何文本表达式,无论是在字段或字符串中格式化为 JSON 的文本,还是在变量中已解析的 JSON。

以下示例解析 JSON 文本:

复制
设置变量 [ $JSON1 ; "{ \"产品\": {\"id\": \"FB1\"} }" ]
设置变量 [ $JSON1 ; JSONParse ( $JSON1 ) ]
  1. $JSON1 只是文本表示,如下所示:

    { "产品": {"id": "FB1"} }

  2. 使用 JSONParse 后:

    • 自动缓存包含二进制表示。

    • $JSON1 变量包含文本和二进制表示。

如果不使用 JSONParse,那么在这个示例中:

复制
设置变量 [ $JSON1 ; "{ \"产品\": {\"id\": \"FB1\"} }" ]
设置变量 [ $JSON2 ; JSONGetElement ( $JSON1 ; "产品") ]
  1. 和以前一样,$JSON1 只是文本表示。

  2. 使用 JSONGetElement 后:

    • 自动缓存包含二进制表示。

    • $JSON2 包含 {"id": "FB1"} 的二进制表示。

    • $JSON1 仍然只是文本表示。文本已被解析,但二进制表示仅在自动缓存中。

与添加 JSONParse 时发生的情况形成对比:

复制
设置变量 [ $JSON1 ; "{ \"产品\": {\"id\": \"FB1\"} }" ]
设置变量 [ $JSON1 ; JSONParse ( $JSON1 ) ]
设置变量 [ $JSON2 ; JSONGetElement ( $JSON1 ; "产品") ]
  1. 和以前一样,$JSON1 只是文本表示。

  2. 使用 JSONParse 后:

    • 自动缓存包含二进制表示。

    • $JSON1 包含文本和二进制表示。

  3. 使用 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 文件时使用的格式无关。