FileMaker 22 JSON Performance Optimizations

Data Types and Type Coercion 

FileMaker hides a lot of complexity that developers from other programming languages have to deal with. When declaring variables, for example, we don’t have to be explicit about the type of data (number, text, etc.) we want to store in the variable. (We do for a field, but not for a variable.) FileMaker handles data typing for us transparently. This works so well that most of the time, we don’t even have to think about it. But occasionally, the underlying reality lurks its head and makes itself known.

For example: 

If 2 < 10 is true, then why is 2 < “10” false? 

“Less than” is a binary operator (it operates on two operands). In the first case, both operands are numbers, so a numerical comparison is made. In the second case, one of the operands is a text string, which causes FileMaker to reinterpret the other operand as a string (this is called type coercion). When two strings are compared in this way, it’s akin to sorting text. The first character in each string is compared, and you only proceed to the next character in case of a tie. 

Value Representations 

When the characters are compared, they are first converted to their code (a numerical value), and then those codes are compared.

  • Code ( “2” ) = 50
  • Code ( “1” ) = 49
  • “2” > “10” because 50 > 49

Scalar vs Compound 

FileMaker has three kinds of variables: local ($), global ($$), and Let variables (declared inside a Let statement). 

At first glance, FileMaker variables appear to be scalar (have just a single value), but under the hood, they are actually compound (have multiple values). For instance, when a date value is stored in a variable, under the hood, FileMaker keeps both a text representation (which is what gets displayed to users) and a numeric representation (number of days since 1/1/0001). 

In fact, each of the different types of variable values can have both a text representation and a native representation for the various non-text types: number, date, time, timestamp, container. 

Parsing JSON Text Creates a Binary Representation 

Just as text and date values have to be converted to a non-text representation for certain operations (e.g., sorting for text, comparison for dates), so do JSON values. When JSON is displayed in FileMaker, it appears to be simply a text value. But if we want to extract a value from it or modify it, FileMaker first has to parse it into a binary representation, because that’s what the underlying JSON-processing library requires. 

Global Cache 

Parsing a text representation of JSON into its binary version is an operation that itself takes time. For faster performance, starting with version 18, FileMaker began caching the parsed, binary JSON. This sped up JSON-processing functions (all the ones that begin with “JSON”), but only so long as the cache was valid. Only one cache per thread was maintained, so as soon as a different JSON value was parsed, the previous cache was discarded. 

  1. Set Variable [ $value1; JSONGetElement ( $json1 ; “sound” ) ]
  2. Set Variable [ $value2; JSONGetElement ( $json1 ; “color” ) ]
  3. Set Variable [ $value3; JSONGetElement ( $json2 ; “size” ) ]
  4. Set Variable [ $value4; JSONGetElement ( $json1 ; “shape” ) ]

Play-by-play (in versions 18-21):

  • After line 1, $json1 is parsed, and the binary representation is saved in the global cache.
  • After line 2, $json1 does not need to be parsed again.
  • After line 3, $json2 is parsed and cached. The previous cache value is discarded.
  • After line 4, $json1 has to be re-parsed.

Variable Cache

Starting with version 22, it is now possible to store the parsed binary JSON in a variable as well as in the global cache. The new functions intended to be used for this are:

  • JSONParse ( json ) – Parses text as JSON data and keeps the parsed JSON representation in memory for you to reuse with other JSON functions.
  • JSONParsedState ( json ) – Returns 0 if JSON is not parsed, -1 if parsed but invalid, or a positive number representing the JSON type if parsed and valid.

Let’s look at an example:

  1. Set Variable [ $json ; “{\”simple\”:{\”nested\”:\”object\”}}” ]
  2. Set Variable [ $json ; JSONParse ( $json ) ]

Play-by-play:

  • After line 1, $json contains only the text representation.
    • It looks like this: {“simple”:{“nested”:”object”}}
  • After line 2:
    • The global cache has been set to the binary representation.
    • $json contains both the text and the binary representations.

Is JSONParse Really Needed?

Is JSONParse really needed if the parse happens on first use automatically? Couldn’t we just use JSONGetElement (or some other JSON function) to trigger the parsing instead of having to use JSONParse? The short answer is “not really”. (For me, the trickiest part about this new feature has been to wrap my head around why that is the answer.)

  1. Set Variable [ $json1 ; “{\”simple\”:{\”nested\”:\”object\”}}” ]
  2. Set Variable [ $json2 ; JSONGetElement ( $json1 ; “simple” ) ]

Play-by-play:

  • After line 1, $json1 contains only the text representation.
  • After line 2:
    • The global cache has been set to the binary representation.
    • $json2 contains the binary representation of {“nested”:”object”}
    • $json1 still only contains the text representation. The text value has been parsed, but that binary representation hasn’t been stored anywhere other than the global cache.
Set Variable [ $json2 ; JSONGetElement ( $json1 ; “simple” ) ]
|
|
binary value
stored in $json2
alongside the text value
|
|
binary value
stored in global cache
($json1 still only has just text)

Contrast With JSONParse

Let’s contrast this with the use of JSONParse:

  1. Set Variable [ $json1 ; “{\”simple\”:{\”nested\”:\”object\”}}” ]
  2. Set Variable [ $json1 ; JSONParse ( $json1 ) ]
  3. Set Variable [ $json2 ; JSONGetElement ( $json1 ; “simple” ) ]

Play-by-play:

  • After line 1, $json1 contains only the text representation.
  • After line 2:
    • The global cache has been set to the binary representation.
    • $json1 contains both the text and the binary representations.
  • After line 3:
    • $json2 contains the binary representation of {“nested”:”object”}
    • (My understanding is that the text representation will not be stored in $json2 at this point but will get generated later when needed; more on this below)

Note that JSONGetElement did not need to parse $json1 on line 3, since it was able to use the variable cache created in line 2.

Caveat

JSONGetElement ( $json ; “” ) appears to simply return the input value after parsing it. So are lines 2 and 3 below equivalent?

  1. Set Variable [ $json1 ; “{\”simple\”:{\”nested\”:\”object\”}}” ]
  2. Set Variable [ $json1; JSONParse ( $json1 ) ]
  3. Set Variable [ $json2; JSONGetElement ( $json1 ; “” ) ]
Let ( [  
  json = "{ \"foo\": { \"hello\" : \"world\" } }" ;  
  json  = JSONGetElement ( json ; "" ) ;  
  state = JSONParsedState ( json )  
] ;  
  List ( json ; state ) 
) 
// => 
// {"foo":{"hello":"world"}} 
// 3 

Place this in your Data Viewer

While the result can appear to be identical, there is a subtle difference in behavior. JSONParse leaves the input text as is. After line 2, $json1 contains the binary value and the original, unaltered text value. After line 3, $json2 contains only the binary value. The text value will only get generated if and when it’s needed (we’ll get into this in some more detail a bit later on). If it does get generated, it will be formatted.

Let ( [  
  json = "{¶ \"foo\": { \"hello\" : \"world\" } ¶}" ; // now has carriage returns
  json_parsed = JSONParse ( json ) ; 
  json_get = JSONGetElement ( json ; "" ) 
] ;  
  List ( json ; json_parsed ; json_get ) 
) 
// => 
// {¶  "foo": { "hello" : "world" }  ¶}
// {¶  "foo": { "hello" : "world" }  ¶}
// {"foo":{"hello":"world"}}

JSONSetElement

We’ve learned that we should parse a $json variable before repeatedly calling JSONGetElement on it. What about JSONSetElement? Do we need to parse before using it? The same underlying rules apply.

This example shows how the alreadyParsed Let variable is already parsed:

Let ( [  
  alreadyParsed = JSONSetElement ( "" ; "foo.hello" ; "world" ; JSONString ) ;  
  state = JSONParsedState ( alreadyParsed )  
] ;  
  List ( alreadyParsed ; state ) 
) 

// => 
// {"foo":{"hello":"world"}} 
// 3 

Place this in your Data Viewer

But this example highlights how, while the result of the JSONSetElement function is parsed, the input JSON variable is not. This is the same situation as with JSONGetElement.

Let ( [  
  json1 = "{ \"foo\": { } }" ;  
  json2 = JSONSetElement ( json1 ; "foo.hello" ; "world" ; JSONString ) ;  

  state1 = JSONParsedState ( json1 ) ;  
  state2 = JSONParsedState ( json2 )  
] ;  
  List ( json1 ; state1 ; json2 ; state2 ) 
) 
// => 
// { "foo": { } } 
// 0 
// {"foo":{"hello":"world"}} 
// 3 

Place this in your Data Viewer

Set Variable [ $json2 ; JSONSetElement ( $json1 ; “simple”, JSONString ) ]
|
|
binary value
stored in $json2
|
|
binary value
stored in global cache
($json1 still only has just text)

Field vs. Variable

We’ve seen how variables can store more than just one representation of a value. The story is different when it comes to fields, though. When a value is stored in a field, only the text representation is stored.

Variables are in-memory entities. Field values are stored in records on the file system. Under the hood, FileMaker stores all field values as text, i.e., a scalar value. In contrast, variables can have one or more value variants at any given point in time. For JSON, this would be text and/or binary.

Let’s explore this difference in behavior:

  1. Set Field [ Test::TextField1 ; “{\”simple\”:{\”nested\”:\”object\”}}” ]
  2. Set Field [ Test::TextField1 ; JSONParse ( Test::TextField1 ) ]
  3. Set Field [ Test::TextField2 ; JSONGetElement ( Test::TextField1 ; “simple” ) ]

Play-by-play:

  • After line 1, TextField1 contains only the text representation.
  • After line 2:
    • The global cache has been set to the binary representation.
    • TextField1 still contains only the text representation.
  • After line 3:
    • TextField2 contains the text representation of {“nested”:”object”}

From Binary Back to Text

Converting text JSON to binary is an operation that takes some time to do. The same is true in reverse (converting binary JSON to text). For this reason, the result of a JSON function like JSONSetElement is just the binary representation. The text representation won’t get created until it’s needed.

An example of what makes it “needed” is viewing the variable in the Data Viewer or storing the variable value in a field (since fields can only store the text representation). As a consequence, this fact is hard (impossible?) to demonstrate. Like some quantum mechanics experiment, every time we look at a variable, if the text representation doesn’t yet exist, it’s going to be generated and then displayed.

Under the Hood

Clay Maeckel did an under-the-hood presentation on this topic at the 2025 Claris Engage and again at Vienna Calling in June of 2025. Claris will be making the recording of his Engage presentation available on YouTube. Look for it if you’re interested to learn more details.

Takeaways

If your code will be working with the same JSON value multiple times, use JSONParse to save a binary representation of that value in a variable first. Remember to use a variable, not a field.

Launching New FileMaker 2025 Features

Our team has already hit the ground running with FileMaker 2025, helping our clients understand the best way to upgrade and the most impactful features to launch in their systems. Need help? Contact our team to talk with an experienced FileMaker consultant.

Don’t miss our other FileMaker 2025 blog posts. Our team is weighing in on critical changes and the best new features for your application.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top