Printable
Version
 

Thanx 2 the Messiah 4 stepping into my life in '96 & staying with me; SixFriedRice who supplied the spark for the idea; Bruce Robertson who prompted me on (and on), and Python writers for their brilliant thinking on tight syntax which i could steal... Further thanks going to Trev Hatchett, Stephan Wolf & Andries Heylen for constructive feedback pushing the project further in various ways.

List, dictionary & JSON functionality in FileMaker Pro [v1.33]

via custom functions, heavily influenced by the pleasure of these abilities in python and other languages

Introduction

  • Summary

      Python, PHP and many other languages have clear object and array structures, they are good and highly useful. This article and attached demo delivers a complete implementation of them for FileMaker. FileMaker does now provide it's own JSON implementation, for which we give thanks!

      I have used the python list (here also) and dict specification as my implementation guide, and have replicated most all of it. It can do a lot of clever things very tightly. See if you are convinced as you follow the worked example below... (downloadable installation guide/demo file also available at end). You will have to be pretty compelled as this could mean installing 16 core custom functions (as opposed to SixFried's 11 total), and of the remaining optional functions: 6 relate to 'For' methods, 1 to JSON, 3 to Script Parameter handling, and 5 other random optional ones...!

      The benefits of the 'dict' functionality are immediate and obvious when it comes to passing parameters in PerformScript calls - credit must go to SFR whose methodology of 2 char separators (and infinite nesting via escaping) set me off on this road - I used it everywhere, but increasingly found I required easier syntax and more powerful manipulation tools: you should read articles one and two from them on that. The value of true 'list' functions you will see in just a little while!

  • Terminology

      JSON.org is very good at this, so go look there if you are confused. I will consistently use JSON to represent the data objects used here.

      dict = dictionary = object [acc. JSON] = associative array = collection of 'pairs'

        (name,value) pair = (key,value) pair

        Single dimensional dict:
        {
              'A Flat': 'Associative Array',
              'Is just': 'A list of',
              'Key': 'Value',
              'Pairs': 'such as this'
        }

        Multi-dimensional dict:
        {
              'A': {
                    'multi': 'dimensional',
                    'associative': 'array',
                    'will': ' have',
                    'two': {
                          'or': 'more',
                          'layers': 'or levels'
                          }
                    },
              'But': 'it need not follow any rigid structure
              'And': 'might have one dimensional parts within it'
        }

      list = array = [ 1, 2, 5, 'sheep', 'dog', 'cat', ..., N ]

        or a nested list thus:

        [ [1, 'one'], [2,'two'], [3,'three] ]
        or
        [ 1, 2, [three, four] ]

        ...but list ≠ list

        Comically, they are NOT the same. The FileMaker Pro 'List' Function is a fine function, but it is not what I mean by a list. FMP's List result is ¶ separated. The one discussed below has its own custom syntax... So beware when i mix the words, i don't always mean the same thing! The one developed in this article can be multi-dimensional, and has a much more robust separator method (thank you SixFriedRice).

  • Current Syntax & Examples

      Here is the syntax I use:

      _  =>  I prefix every custom function variable, every 'Let' statement variable with this, incase it should ever become an actual function in the next generation FMP - one that caught a lot of people out was 'char'. Now a function, but if it was a standard variable in your funcs you were in trouble...
      | & #...  =>  denoting 'List' and 'Dict' - prefixing the appropriate functions
      <:key:=value:>  =>  pair syntax (a dict is formed of a concatenated collection of the same) - this is nicked straight from SFR...
      <|-list_item_1|-list_item_2|->  =>  list syntax
      <|-> (empty list)

      On reflection I might have been better to code for full JSON compliancy in reading (not just output), or at least using the same character as the escaped separator... but hey!

  • $variable questions

      These custom functions depend heavily upon variable usage, as they have certain unique functionality that allows tighter code, this is exploited heavily in these custom functions. This is further explained at end. It should not phase you, $local variables dissappear outside of script so they need not clutter.

      This creates a small limitation on SFR's noble technique, the restrictions that apply to fieldnames now apply to the dict name (no bad thing), i.e. no return separated dict name may be used. This version alters SFR escape model fractionally by just escaping ":" to "/:/", however, this is easily changed in one location: the #Escape function.

Custom Function Information

      These are NOT IN INSTALLATION ORDER - this is provided in demo - please note |Tool is an unavoidably interdependent function and must therefore be installed blank with just the parameters in place at first... Then at the end be sure to go back to it and install |Tool's code. For more custom function comments see list in demo.

  • Core Invisible Functions: |Tool, #Tool, #Escape...

      It is my sincere hope that, unless you are adding more python etc serious functionality (in which case be sure to let me know and send me a copy of the working function), you will never *ever* make a call on these functions! They are, however, essential background helper functions and kept separated to reduce future confusion.

      They are documented internally if you need to follow them.

  • Simple Common Functions: _, #Count, IsNumber, #VarType, #JSON...

      _, #Count, #VarType, IsNumber

        _ - yes, that is it! The 'underscore' character and it just results in “” - it's keeps the code clean and tidy, and I use it everywhere for sending blank params.

        #Count ( _dictORList ) returns length of any list or dict passed to it.

        It depends upon #VarType ( _var ) to detect the variable type passed to it.

        IsNumber is used often to work out whether a given list member, or dict value is a number, and it needs to apply FMP's 'GetAsNumber' Function in order to evaluate it appropriately in statements etc... The reason it is heavy is cos -12.506e+5 is a number... And i can't see no easy way to determine it. If you can please let me know cos i accept the function is overweight!

      #JSON (fka #Print)

        26.1.2011: noted 10k character limit on reading JSON objects.

        My motivation in producing this technology has been the clean resolution of complex internal problems (I am an in house developer). In particular the handling of part items and orders of stock items that are 'virtual' - that is entirely made up of other 'real' items. I created a bogus example for the demo of a car part supplier, they supply parts, but also full kits to assemble cars. When you order a Nissan Micra car kit it needs to check stock availability across all component parts, so a little array is assembled (more on how later)... As a raw string it looks like this:

        <|-</|/-PS0007/|/-500kg Metal/|/-3/|/-50/|/->|-</|/-PS0004/|/-Door/|/-5/|/-100/|/->|-</|/-PS0002/|/-Nut/|/-1000/|/-15000/|/->|-</|/-PS0008/|/-Seat/|/-4/|/-100/|/->|-</|/-PS0012/|/-1Litre Petrol Engine/|/-1/|/-3/|/->|-</|/-PS0003/|/-Steering wheel/|/-1/|/-10/|/->|-</|/-PS0005/|/-Window/|/-6/|/-50/|/->|->
        Values = [ ID, Description, Part Quantity, Stock ]

        Pretty, hey? Well, the machines like it, but we can't understand it so easily: so I made a #JSON function to display in the more readable JSON format (optionally add "num" to end of a 'DictList' to get numbered lists/arrays),#JSON ( _input ).

        #JSON ( $Nissan_Micra_Kit & "num" ) results thus...

        [
              0 =>  [0 =>  "PS0007", 1 =>  "500kg Metal", 2 =>  3, 3 =>  50],
              1 =>  [0 =>  "PS0004", 1 =>  "Door", 2 =>  5, 3 =>  100],
              2 =>  [0 =>  "PS0002", 1 =>  "Nut", 2 =>  1000, 3 =>  15000],
              3 =>  [0 =>  "PS0008", 1 =>  "Seat", 2 =>  4, 3 =>  100],
              4 =>  [0 =>  "PS0012", 1 =>  "1Litre Petrol Engine", 2 =>  1, 3 =>  3],
              5 =>  [0 =>  "PS0003", 1 =>  "Steering wheel", 2 =>  1, 3 =>  10],
              6 =>  [0 =>  "PS0005", 1 =>  "Window", 2 =>  6, 3 =>  50]
        ]

        Pure JSON output is the default handling for DictList format input, so:

        Let ( $JSON_Nissan_Micra_Kit = #JSON ( $Nissan_Micra_Kit ) ; $JSON_Nissan_Micra_Kit ) =>

        [
              [ "PS0007", "500kg Metal", 3, 50 ],
              [ "PS0004", "Door", 5, 100 ],
              [ "PS0002", "Nut", 1000, 15000 ],
              [ "PS0008", "Seat", 4, 100 ],
              [ "PS0012", "1Litre Petrol Engine", 1, 3 ],
              [ "PS0003", "Steering wheel", 1, 10 ],
              [ "PS0005", "Window", 6, 50 ]
        ]

        It will happily print nested and mixed dict/lists too...

        Best of all, however, as of version 10, you can send it JSON objects and it will recognise them, and convert them to DictList format, so:

        #JSON ( $JSON_Nissan_Micra_Kit ) =>

        <|-</|/-PS0007/|/-500kg Metal/|/-3/|/-50/|/->|-</|/-PS0004/|/-Door/|/-5/|/-100/|/->|-</|/-PS0002/|/-Nut/|/-1000/|/-15000/|/->|-</|/-PS0008/|/-Seat/|/-4/|/-100/|/->|-</|/-PS0012/|/-1Litre Petrol Engine/|/-1/|/-3/|/->|-</|/-PS0003/|/-Steering wheel/|/-1/|/-10/|/->|-</|/-PS0005/|/-Window/|/-6/|/-50/|/->|->
        i.e. exactly as it was before we converted it to JSON in order to read it.

        So we can now do JSON posts and receive them in FileMaker, useful for all sorts of options, not least internet interactivity...

  • List (or array) functions: …, |List, |VL, |2VL, |Zip, |Comp, |Index, |Min, |Max, |For...

      … & |List

        These are key for forming Lists, the … custom function (CF) provides the means of separating list items, it turns into '^_^' (and assumes you will never have this in your text). If you are |Listing items that are themselves Lists, it will automatically separate them - you don't need to use the '…' CF.

        |List("ID" & … & "Description" & … & "Part Quantity" & … & "Stock") => <|-ID|-Description|-Part Quantity|-Stock|->
                    
        Let ( [
              List1 = "<|-0|-1|-2|->" ;
              List2 = "<|-zero|-one|-two|->" ] ;
              |List ( List1 & List2 )
        => 
        <|-</|/-0/|/-1/|/-2/|/->|-</|/-zero/|/-one/|/-two/|/->|->

      |VL, |2VL

        |VL converts FMP ValueLists into 'proper' Lists, |2VL converts 'proper' Lists to ValueLists. The use below is to begin to get useful data via the List function for my part info stock query...

        Let ( [
              _list = |List (
                    |vl( List ( O_OI_PS_child_PSPI::_id_PS ) ) &
                    |vl( List ( O_OI_PS_child_PSPI::Description) ) &
                    |vl( List ( O_OI_PS_child_PSPI::PartQty ) ) &
                    |vl( List ( O_OI_PS_child_PSPI::Stock ) )
                    )
        ];
              #Print ( _List ; "JSON" )
        )

         => 

        [
              [ "PS0007", "PS0004", "PS0002", "PS0008", "PS0012", "PS0003", "PS0005" ],
              [ "500kg Metal", "Door", "Nut", "Seat", "1Litre Petrol Engine", "Steering wheel", "Window" ],
              [ 3, 5, 1000, 4, 1, 1, 6 ],
              [ 50, 100, 15000, 100, 3, 10, 50 ]
        ]

        Not bad hey? We've got the data, but how to get them in related rows? That's the question I muttered under my breath, and fortunately a python programmer named Jonny was sitting next to me, "Zip" he said. And so it was...

      |Zip ( _list )

        Understood best by seeing it at work (to avoid repetition I'm just using variables defined in preceding examples).

        #Print ( |Zip ( |List ( List1 & List2 ) ) ; "JSON") => 
        [
              [ 0, "zero" ],
              [ 1, "one" ],
              [ 2, "two" ]
        ]

        Let ( _zipped = |Zip ( _List ) ; #Print ( _zipped ; "JSON") ) => 
        [
              [ "PS0007", "500kg Metal", 3, 50 ],
              [ "PS0004", "Door", 5, 100 ],
              [ "PS0002", "Nut", 1000, 15000 ],
              [ "PS0008", "Seat", 4, 100 ],
              [ "PS0012", "1Litre Petrol Engine", 1, 3 ],
              [ "PS0003", "Steering wheel", 1, 10 ],
              [ "PS0005", "Window", 6, 50 ]
        ]

      |Comp ( _expression ; _vars ; _list ; _condition )

        I'm now ready for this wondertool of a function. Say I want to place an order for 10 Nissan Micra Kits, but I want it to stop me if I don't have sufficient parts. How to do it?? |Comp is your friend! >>>

        Let ( _comp = |Comp (
              "partQty*10…id…desc…partQty…stock" ;
              "id…desc…partQty…stock" ;
              _zipped ;
              "(partQty*10)>stock"
              ) ;
        #Print ( _comp ; "JSON" )  )
        => 
        [
              [ 10, "PS0012", "1Litre Petrol Engine", 1, 3 ],
              [ 60, "PS0005", "Window", 6, 50 ]
        ]

        Nice hey? I could also say something like...

        Let ( _comp2 = |Comp (
              "Int ( stock / partQty )" ; // i.e. the amount of whole units that can be fulfilled given current stock
              "id…desc…partQty…stock" ;
              _zipped ;
              _ ) ;
        #Print ( _comp2 ; "JSON" ) )
        => [ 3, 8 ]

      |Index ( _list ; _value ), |Min ( _list ), |Max ( _list )

        Pretty basic functions, |Index will return index of first exact match in a list.

        |Min is handy in our situation cos it'll help us find the maximum number of Nissan Micra Kits we can supply:

        |Min ( _comp2 ) => 3

      |For ( _keys ; _list )

        This function is very fine indeed. Here is a picture of it in use in the demo (you'll note you can send it nested lists as the _keys and this does funky stuff a la python).

        A #ForEach ( _dict ; "key" ; "value" ) dictionary function is redundant, as it can easily be replicated by |For ( "key…value" ; #Items ( _dict ) ). Which leads us on to our next batch of functions.

  • Dictionary (or object, or associative array) Functions

        To introduce the dict functions, this is my example dictionary for the rest of the article:

        {
              "Bruce": {
                    "Address": {
                          "Street": "71 Madison Ave",
                          "Postcode": 90124
                    },
                    "id": "STA003",
                    "Preferences": {
                          "Tooltips": 0
                    }
              },
              "Jesse": {
                    "Address": {
                          "Street": "6 Fried Rice Ave.",
                          "Postcode": 39287
                    },
                    "id": "STA002"
              }
        }

        We will look at methods of building such a dict in a few moments...

      get: #g ( dict [ key ] { [ ... ] [ key n ] } )

        For the next few examples we will assume we have stored the dictionary above in both the field GLB::Staff, $People, and "$$staff for my company" (an acceptable variable name!) then this function would give the following results used in the following ways:

        #G("$People[Jesse][Address]") => "<:Street:=6 Fried Rice Ave.:><:Postcode:=39287:>"
        #G("GLB::Staff[Bruce][id]") => "STA003"
        #G("Staff[Bruce][Address]") => "<:Street:=71 Madison Ave:><:Postcode:=90124:>"
        #G("$$staff for my company [ Jesse ] [ Postcode ]") => "39287"
        while an erroneous call will return blank...
        #G("Staff[Fred]") => ""

        This function can only 'get' from variables/fields it can evaluate, i.e. not Let Statement values.

        This is only the 'dict' functionality. See common manipulators for a whole heap more cunning involving lists (same applies for #Get, #D and # below).

        Two additional shortcuts Bruce & SFR's thinking got me to include are 'P' and 'R' being evaluated as 'Get(ScriptParameter)' and 'Get(ScriptResult)'. Usage:

        #G("P[myScriptParam]") [but why use this when you could use SFR's shorter #P("myScriptParam")?
        #G("R[myScriptResultName]") [again you could just use #R("myScriptResultName")]

        If you aren't seeing the benefits of using such dicts as your script parameters everywhere then you perhaps need to check SFRs original articles - referenced above, and also consider the use of #R, #P & #Assign below...

      simple get: #get ( dict ; [ key ] { [ ... ] [ key n ] } )

        This is for getting straight from any dictionary, i.e. Let Statment dicts too

        Arguably this could have been combined with #G, and so it could, but the task was too much for me at the time, it is used everywhere, and I couldn't face it, and with Let Statement var usage it makes for more readable code, so I've not got a problem with keeping it around. Also #P and #R would need a little recoding!

      delete: #d ( dict [ key ] { [ ... ] [ key n ] } )

        Here we come to one of the big distinctions in how #D and # work with variables as opposed to with fields.

        Field usage

          If you use a field it will return the result, i.e.

          SetField [ GLB::Staff ; #D ( "GLB::Staff[Bruce][Address]" ) ] sets GLB::Staff to:
          {
                "Bruce": {
                      "id": "STA003",
                      "Preferences": {
                            "Tooltips": 0
                      }
                },
                "Jesse": {
                      "Address": {
                            "Street": "6 Fried Rice Ave.",
                            "Postcode": 39287
                      },
                      "id": "STA002"
                }
          }

          If you tried to delete two items in the same 'SetField' you would break the dictionary and double it unwittingly... you would have to use two separate 'SetFields' to do so, or perhaps you could use SFR methods, which though easy enough at 1D (1 dimension), once u've tried writing the code to delete both "Bruce" and "Jesse"'s "id" - while leaving the rest of "Jesse" intact u'll see it is not so easy. I just tried and gave up. If u might need this read on, cos you'll be using variables!

          You can also use Let Statement variables thus: #D ( _dict & "[key]" ) or #D ( _dict & "key" )

        Variable usage

          To do the above challenge using variables is simple...:

          SetVariable [ $dummy ; #D ( "$People[Bruce]" ) & #D ( "$People[Jesse][id]" ) ]
          This has altered the value of $People but not returned anything to $dummy, $dummy is empty. However, if I had added at the end a simple " & $People", $dummy would have returned thus:
          {
                "Jesse": {
                      "Address": {
                            "Street": "6 Fried Rice Ave.",
                            "Postcode": 39287
                      }
                }
          }

          Nice and tidy, and powerful...

          Anywhere you have a calculation dialog box you can therefore manipulate these dicts. I sometimes set a ScriptTrigger to run a 'DoNothing' script, cos all i need can be done via the calculation box provided for sending script parameters: I can alter my dictionary and execute a screen refresh (to affect conditional formatting controlled by a dictionary). This helps keeps scripts to a minimum, and such layout specific code on the layout to which it applies.

      set/update/insert: # ( dict { [ key ] [ ... ] [ key n ] } ; value )

        Now the fact remains that I still build many objects using SFRs straight usage of #, cos it is simpler and easy to read in most circumstances. There are two ways of building the above array, using this method or using pure SFR:

        SFR #Set methodology:

          # ( "Bruce" ;
                # ( "Address" ;
                      # ( "Street" ; "71 Madison Ave" ) &
                      # ( "Postcode" ; "90124" )
                ) &
                # ( "id" ; "STA003" ) &
                # ( "Preferences" ;
                      # ( "Tooltips" ; "0" )
                )
          ) &
          # ( "Jesse" ;
                # ( "Address" ;
                      # ( "Street" ; "6 Fried Rice Ave." ) &
                      # ( "Postcode" ; "39287" )
                ) &
                # ( "id" ; "STA002" )
          )

          To my mind the distinct advantage of building arrays in this method is when I am creating an entire dict, or discreet unit within one, like Fred's address in example below...

        Alternate #Set methodology:

          The following simply creates $People or adds to/adjusts it if it already exists, it returns no result.

          # ( "$People[Bruce][Address][Street]" ; "71 Madison Ave" ) &
          # ( "$People[Bruce][Address][Postcode]" ; "90124" ) &
          # ( "$People[Jesse][Address][Street]" ; "6 Fried Rice Ave." ) &
          # ( "$People[Jesse][Address][Postcode]" ; "39287" ) &
          # ( "$People[Bruce][id]" ; "STA003" ) &
          # ( "$People[Jesse][id]" ; "STA002" ) &
          # ( "$People[Bruce][Preferences][Tooltips]" ; "0" )

          Now this is harder to write frankly for the same array. So you don't use it for such things. However, say I passed this as a parameter, and before I passed it back, I wanted to alter Jesse's ID, and add Fred's data? Its at this point that this method really comes into its own for simplicity and speed...:

          # ( "$People[Jesse][id]" ;"STA001" ) &
          # ( "$People[Fred][id]" ; "STA004" ) &
          # ( "$People[Fred][Address]" ;
                # ( "Street" ; "6 Fried Rice Ave." ) &
                # ( "Postcode" ; "39287" ) &
                # ( "State" ; "California" ) &
                # ( "Country" ; "USA" )
          )

          You will note in the above that I combine both methods, cos I think both are useful! Whatever it takes to make clear, legible, and quick-to-write code...

          You can use Let Statement variables and fields as with #D, but only when adding only one level or not dynamically.

      #Keys ( _dict ),  #Values ( _dict ),  #Items ( _dict )

        These all result in 'lists'.

        #Keys: This simply returns a list of all the top level keys in the dict in a List format (not ValueList, but list as defined above).

        #Values: This returns a list of all the values in a list format.

        #Items: This returns a nested list of [name,value] pairs, i.e. [ [name,value], [otherName, otherValue], ... ] etc. This is particularly handy for iterating through dicts in |For loops, or creating value lists thus: |2vl ( #Keys ( _dict ) ) - will return a ¶ separated list from the keys of the dict.

      #Contains ( _dict ; _key )

        Equivalent to pythons 'key in'.

      #Lists2Dict ( _keys ; _values )

        Send matching length lists or ValueLists, or a mixture (it will immediately convert them into correct list format if it receives value lists), and it will output a dict. To revert to our car parts example earlier, it allows you to do stuff like this:

        #Lists2Dict ( List ( O_OI_PS_child_PSPI::_id_PS) ; _zipped ) => 
        {
              "PS0007": [ "PS0007", "500kg Metal", 3, 50 ],
              "PS0004": [ "PS0004", "Door", 5, 100 ],
              "PS0002": [ "PS0002", "Nut", 1000, 15000 ],
              "PS0008": [ "PS0008", "Seat", 4, 100 ],
              "PS0012": [ "PS0012", "1Litre Petrol Engine", 1, 3 ],
              "PS0003": [ "PS0003", "Steering wheel", 1, 10 ],
              "PS0005": [ "PS0005", "Window", 6, 50 ]
        }

        I don't really now need the id in the actual data row, but nevermind...

      Script Passing Techniques:  #P,  #R  &  #Assign

        #P ( _name ) will grab value of _name specified from assumed dict in Get(ScriptParameter), #R ( _name ) will do the same for ScriptResult.

        This is just an extra thrown in for those who intend to pass dicts as their script params. Matt Petrowsky of ISO FileMaker Magazine introduced me to this CF which has been made by Alexander Zueiv. It asks you to write your scripts in form thus:

        MyScript [ paramA ; ( paramB | paramC ) {; paramD } ]

        It will then go thru your script params and convert them into $locals, or die if compulsory variables have not been sent - check his documentation in the function. I have it in my template script near the top of every Script. One disadvantage of this technique is in sending blank value dicts, i.e. <:myField:=:>, then it will set variable $myField to "", and it will of course then disappear and think it is undefined, generally however, it is very useful!

  • Common Manipulators:  #,  #D,  #G,  #Get

        While developing dicts and lists it occurred to me that it would be nice if the same methods could be used to create, update, delete and get. So you could mix and control dicts & lists at multi-dimensional levels easily, and that's what you can do with these functions. It works as you would expect, with a few bonuses of note re: lists...

        For purposes of below examples we will set _list & $list to:

        [ 0, 1, 2, 3, 4, 5, 6, 7 ]

      Lists Indexes

        The first item in a list is regarded as index '0'.

        #Get ( _list ; 0 ) returns "0" (the first value), as does #G ( "$list[0]" )

        If you use a negative index it will work back from the last item...

        #Get ( _list ; #Count ( _list ) - 1 ) OR #Get ( _list ; -1 ) => 7 (the last item)
        #G ( "$list[-2]" ) => 6 (the penultimate item)
        #D ( _list & "-1" => [ 0, 1, 2, 3, 4, 5, 6 ] (deletes the last item)
        # ( _list & "[-5]" ; "newt") => [ 0, 1, 2, "newt", 4, 5, 6, 7 ] (if _list was not as long as 5 then it would return _list unaltered)

      List Item Create

        Equivalent to append in python, but the php syntax "[]" suited me here (time for a multi-dimensional example, but the above also work multi-dimensionally):

        #( "$dict[Tom][ToDo][]" ; "Opticians, 1.15pm" ) => If $dict[Tom][ToDo] is a list it will add "Opticians, 1.15pm" at the end.

      List Slice (taken from python wholesale)

        All behaviour has been a simple copy of python slice behaviour, the crucial aspect is thus: [start:end:stride], with every aspect being optional, except there must be one colon!

        #Get ( "$list[:]" ) => [ 0, 1, 2, 3, 4, 5, 6, 7 ]
        #Get ( _list ; ":4" ) => [ 0, 1, 2, 3 ] (if start is blank defaults to start)
        #G("$list[4:]") => [ 4, 5, 6, 7 ] (if end is blank defaults to end)
        #Get ( _list ; "[1:4]" ) => [ 1, 2, 3 ]

        See the stride in use (if stride is blank, defaults to 1):
        #G("$list[::3]") => [ 0, 3, 6 ]
        #G("$list[::-1]") => [ 7, 6, 5, 4, 3, 2, 1, 0 ] (a neat method of reversing a list...)

        I can't be bothered to give full syntax and explanation, why duplicate what python has done and documented! A little imagination will tell you the 'slice' is a powerful concept in list handling. Suffice it to say, #D and # on slice also perfectly duplicate python (as far as I have tested):

        #("$list[2:2]"; |vl ( "hello¶my¶hearties" ) ) => [ 0, 1, "hello", "my", "hearties", 2, 3, 4, 5, 6, 7 ]

        If you find failure at any point in python consistency let me know and I will correct.

Notes

      Please contact me if you develop any of the other standard list & dict CFs; or require clarifications of particular areas...

  • Reserved Characters

      ^_^ = do not use this character combination in text or anywhere that you wish to use in lists at any point...

      [] = do not use in dictionary key names

      …/elipsis = do not use in place where it will be 'evaluated' (unless you want them to be expanded), |comp expands them, as does |for, it's unlikely to happen but just in case. I use this as a short cut for writing tidy, easy to read code, it expands to create correct list syntax

  • Installation

      Installation order for the 30 custom functions in file/demo [155KB].



footnotes

    Links in footnotes were correct at time of writing. If you find dead ones, please let me know. In a number of cases I have saved a copy of the linked page/file, and can make this available on request.

  • 1: ^   Version 1.33:   |For using $local_variables (much faster & safer); |Tool JSON creation converts European decimals; GetAsVarType has some changes made here (hopefully) in alignment with TSWolf feedback, but not sure cos babybrain thanks to little Lily's arrival 20th August 2017! IsNumber re-introducing 1.25 trailing zeroes ban from being number (as prevent 2dp payment card submissions/CV2s working etc.; NEW functions: |ListRepetitions & #GetVL

  • 2: ^   Version 1.32:   |Tool (JSON_Read section) & #JSON adjusted to fix 1.31 JSON conversion break noted by TSWolf. My bad, I've been inundated in graft and ill health this last year - and a blessed baby! Secondary fix to #VarType, as I'm not happy with weak JSON sniffing located there!

  • 3: ^   Version 1.31:   #VarType, IsNumber, GetAsVarType, |Tool & #Get adjusted to accept international variant formats for timestamp, date and numbers - differing decimal marks and order. TSWolf must have a special mention here for hard and patient work in preparing vaste test datasets, and intricate detailed tests on IsNumber.

  • 4: ^   Version 1.30:   # adjusted to accept timestamp, time and date keys; #Dict2Var adapted to fail gracefully if such keys are met (forbidden variable name characters converted to underscore) and #Count will utlise #G to obtain dictionary/list form a reference, if necessary - so #Count("$d[errors]") works identically to #Count(#G("$d[errors]")).

  • 5: ^   Version 1.29:   |Tool adjusted to accept True/False booleans converts to 1/0.

  • 6: ^   Version 1.28:   IsNumber adjusted to reject - and -0 as numbers!

  • 7: ^   Version 1.27:   |Zip switched to tail end recursion to handle bigger lists (|Tool altered).

  • 8: ^   Version 1.26:   IsNumber tiny adjust to fix for 1.0E+20 being disqualified by trailing zero fix in 1.25.

  • 9: ^   Version 1.25:   IsNumber & #VarType fixed to improve handling. IsNumber no longer recognises 0910 (say a card expiry number) or 3.10 (2dp currency) as numbers, leading/trailing zeroes would then be lost so they must be treated as text. Then found #VarType was evaluating 0910 as a time, so further tests there to prevent this occuring.

  • 10: ^   Version 1.24:   A tiny change - Repeat has gone (|Tool & #Escape changed) as not needed since Nils Waldherr's (http://www.filemakergarage.com) neat solution has been used instead to limit calls to external and recursive functions. A theoretical limit now exists - dicts and lists ought be no more than 400 layers deep! ;)

  • 11: ^   Version 1.23:   IsNumber, #JSON, |Tool, #Get changed. Thanks to Andries Heylen for pushing significant JSON reading improvements in speed (4x faster) and handling. Previous JSON data limit has been substantially grown. #VarType recognises JSON as starting with { or [. Andries Heylen has adapated IsNumber to handle European decimal points (commas not full stops). It also recognises booleans (true/false) as numbers enabling better handling of these types.

  • 12: ^   Version 1.22:   |Tool adjusted to allow the insertion of an empty item on creation of a list by #( "$thisMeans[]" ; "" ).

  • 13: ^   Version 1.21:   #VarType updated to include Ray Cologon's Trim4 and thus be more flexible in use.

  • 14: ^   Version 1.20:   #VarType has changed subtly - failure to properly implement my date, time and timestamp checking system from last version - date checker was failing.

  • 15: ^   Version 1.19:   After a coding holiday in Spain, I discovered problems with data types, which I have possibly not fully resolved. I expect to see them occur in |Comp in fact, cos I need to replicate the fix in #Dict2Var more thoroughly throughout |Tool. This release has resolved problems of the moment, however, #VarType, #Dict2Var & |Tool have changed subtly. New function GetAsVarType now exists.

  • 16: ^   Version 1.18:   # changed to re-allow #("1" ; "One") - coding weakness didn't appreciate I might want "1" to be a key... so it disallowed anything that wasn't text. Which it didn't used to do...

  • 17: ^   Version 1.17:   #Dict2Var created, so I can create global/local variables quickly and easily from dicts. Also noted #JSON 10k character limit.

  • 18: ^   Version 1.16:   # changed, so I can pass field names with repetition value thus: fieldname[repetition]. I pass a dictionary of (fieldname, value) pairs to set fields routinely, and just found a problem: this script was reading fieldname[repetition] as if fieldname should be a dict/list (or variable containing one) and thus died. Whereas if I don't pass a dict/list/$varname then I want it to let it pass. Allows me to use my SetField (internal script not in this demo) to set specific values of repetitions.

  • 19: ^   Version 1.15:   |Tool issues once again, same as last version. Stupid Thomas, I should have used FMP's Quote function to resolve issue then! I needed to cover not just line breaks, but quotation marks too! The use of Quote in |Tool resolved problem.

  • 20: ^   Version 1.14:   |Comp works if passed values containing valuelists - which previously were broken by internal use of FM's evaluation command. Only |Tool was changed.

  • 21: ^   Version 1.13:   |Zip works more robustly if passed lists shorter than one, #G ( _letVar & "[0]" ) is a valid call.

  • 22: ^   Version 1.12:   A tiny modification to |Tool - under certain unusual circumstances returns in value lists were not being correctly handled.

  • 23: ^   Version 1.11:   |2vl merged => |vl - it now intelligently detects what you send it. This was simply to reduce the number of functions by one - |Max & |Min need references changing to |vl instead of |2vl. Scripts rewired to work. Also marked which are 'Core' and which are optional or for other purposes.

  • 24: ^   Version 1.10:   #Print ( _var ; _method ) => #JSON ( _input ) - if you input JSON you get DictList format out & if you input DictList you get JSON out - this is a BIGGIE in actual use terms. It is very fast too. I decided to make 'Repeat' function necessary (figuring you most probably have a useable one already installed, or will do): this made |Tool more robust for infinite (potential) indenting, and eliminated half #Escape recursion.

  • 25: ^   Version 1.09:   |Comp corrected to return empty list if empty result. |vl corrected to do likewise. Realised should have stuck 100% to python! # also documents very useful slice/insert methodology in function text, I forgot how to use it, so wound up having to re-read the code to get it working!!!

  • 26: ^   Version 1.08:   |Comp - _list can now be ValueList - will auto-convert; #Contains handles partial queries like: "que*" or "*ery" - now equivalent to |Index(_list))

  • 27: ^   Version 1.07:   IsNumber simplified, 20% faster too!

  • 28: ^   Version 1.06:   

      |For - now accepts ValueLists as well as lists - just found in practice I need to cycle thru value lists often;
      # & #D - this is the major change codewise, but probably unused! It was behaving strangely in certain instances of sending literal List/Dicts to it, rather than fields/variables to evaluate, resulting in "?" evaluation errors.

  • 29: ^   Version 1.05:   

      |Index - now accepts partial queries - "*tial" or "par*";
      #Get - handles value list queries: #Get ( _dict ; "key1¶key5¶key2" ) etc - dict keys must conform to filemaker naming conventions - no linebreaks - returns List of results—accidentally works on lists too, i.e. #Get ( _list ; "1¶2¶3¶5¶7¶11" );
      #FindKey - new: finds key in _dict for a given _value..., accepts partial queries

  • 30: ^   Version 1.04:   

      |For cleaned, recursive |For script demo'd

  • 31: ^   Version 1.03:   

      slice insert corrected, |Tool & # changed.