FileMaker Dictionary Functions

Since many people were impressed with my named parameters article, I decided to write up another post about the dictionary functions. Actually in all fairness I think three people actually liked it. Vincenzo Menanno, Chris Wack and Sam Barnum this post’s for you!

>Note: This article was revised November 23, 2007. I added the handy [`DictReplace`](#replace) function. -Geoff

First I must warn you, I have left the names of the custom functions the same as the functions we use internally. This means they may seem a little obtuse so feel free to change them. I’m also not getting to in-depth into how they actually work because it could take an extra 5 pages or so (yea I know Geoff… I’m lazy… deal with it). I’m sure most of you will get them pretty quickly despite my inept explanations. Oh yea, I also suggest going back and reading the named parameters post before getting into this post otherwise it probably won’t make much sense.

Enjoy…

> Note: For the duration of this article I will refer to the custom function `PassParameter( name ; value )` in my previous post as #( ). This saves me 12 key strokes and won’t confuse me because that’s what I call it internally.

##Using a Dictionary in the Script Result

I actually posted this in the comments of the Named Parameters post. The idea here is that you can not only use the named parameter idea in your script parameters but in your script results as well. So just place your dictionary values in the script result dialog of the `Exit Script` script step and you’ve go yourself named script results. I like to create a little shortcut to access them like so:





Name: #R ( name )

Definition:

DictGet( Get(ScriptResult) ; name )

##Checking for a Name in the Dictionary

There’s also a point where you may want to check if your dictionary contains a certain value. You could do this by simple using your `DictGet()` custom function on your Dict. By design, a call to the `DictGet()` will return empty if the name you are looking for isn’t there. That’s when I made `DictContains?`


Name: DictContains?( dict, name )

Definition

Let(
pattern = “<:" & name & ":="; Position(dict; pattern; 1; 1) > 0
)

All this is doing is checking the dictionary structure for the name of the value you are looking for and returning true if it finds the value and false if it doesn’t.

> Note: This will not find any nested dictionary values. So if you were to store a dictionary within a dictionary ie. #( “Account” ; #( “Name” ; “Jesse” ) & #( “Job” ; “Writing confusing custom functions” ) ) you wouldn’t be able to use the `DictContains?` custom function to see if `Job` is in the dictionary because it is inside the account dictionary.

## Removing an Entry from a Dictionary

You never know when, but there will be a point where you think… “Man, I wish I could remove a value from my dictionary.” Well, maybe you won’t… but I wrote this thing anyway. If you happen to bump into this situation, you can use the nifty function below. `DictRemove()` simply removes the dictionary name/value pair specified from the dictionary passed in.





Name: DictRemove( dict, name )

Definition:

Let(
[
pattern = “<:" & name & ":="; entry_start = Position( dict ; pattern ; 1 ; 1); entry_end = Position( dict ; ":>” ; entry_start + 1; 1);
dict_beginning = If( entry_start > 0 ; Left ( dict ; entry_start – 1 ) );
rest_of_dict = Middle( dict ; entry_end + 2 ; 999999 );
new_dict = dict_beginning & rest_of_dict
];

If( entry_start > 0 ; new_dict ; dict )
)


##Replacing an Entry in a Dictionary

This function is primarily for convenience. It combines `DictRemove` with a new entry to effectively replace a value in the dictionary in one shot:

`Name: DictReplace ( dict, name, value )`

DictRemove(dict, name) & #(name, value)

##Returning the Top Name in a Dictionary

This custom function was designed to return the name of the first name-value pair in the dictionary. Nothing too special, but I if used in conjunction with the `DictRemove()` function above, you can pop off the top value of the dictionary with ease.


Name: DictFirst( dict )

Definition:

Let(
[startValue = “<:"; endValue = ":="; positionOfStartValue= Position( dict ; startValue ; 1 ; 1 ); endOfStartValue= If( positionOfStartValue > 0 ; positionOfStartValue + Length(startvalue); -1 );
beginningOfEndValue= Position( dict; endValue; endOfStartValue; 1 );
found = If( beginningOfEndValue > -1 and endOfStartValue > -1 ; True; False );
lengthFoundValue = beginningOfEndValue – endOfStartValue;
foundValue = Middle( dict; endOfStartValue ; lengthFoundValue )];

If( found ; foundValue ; “” )
)

## Adding to a Dictionary

This custom function is ridiculously easy. It’s really just a shortcut that looks a little more succinct then actually performing the action manually.


Name:DictAdd( dict; name; value )

Definition:

dict & #( name ; value )

## Dictionary To String

This is quite frankly my favorite custom function ever. Serioulsy, I have a ton of ridiculous functions that no one uses but me, but this is the creme de la creme. I’ll get slightly more indepth about this function because it can be a little tricky to understand. Here is the signature of the function:


Name: DictToString
Parameters:
Dict: A dictionary of your choosing
Format: format you would like to display the data in.

The real magic of this function comes from the `Format` parameter. This parameter will dictate how you want the data displayed. The function goes through each and every dictionary entry and will put it into whichever format you specific. To achieve this functionality a replace is performed on the `Format`. $name is replaced with the name of the dictionary entry and $value is replaced with the value of the dictionary entry. Here is the function:


Name: DictToString( Dict ; Format )

Definition:

Let(
[
firstName = DictFirst ( dict );
rawdata = DictGet(dict; firstName) ;
data = If( not IsEmpty( DictFirst( rawdata ) ) ; DictToString( rawdata ; Format ); rawdata );
rest = DictRemove( dict ; firstName )

];
If( not IsEmpty(firstName) ;
Substitute(
format;
[“$name”; firstName ];
[“$value”; data ]
)
&
If (
not IsEmpty(rest);
DictToString(rest; Format)
)
; dict )
)

Let’s say your `Format` parameter is:

`”$name is $value”`

and your dictionary is

`<:Jesse:=nuts:>`

the output would be

`Jesse is Nuts`

You can now basically output your dictionaries in any format you so desire. Such as…. you guessed it…. XML.

## Dictionary To XML

So once you understand the `DictToString()` custom function, the `DictToXML()` function is just a shortcut that I wrote so I don’t have to write the funky format over and over. Here is is:


Name: DictToXML ( dict )

Definition:

DictToString ( dict , “<$name>$value<$name>” )

Let’s say you setup your dictionary like so #( “account” ; #( “name” ; “Jesse” ) & #( “birthday” ; “Yesterday” ) ). You’ll notice that I have put a pair of dictionaries within another dictionary named account. The DictToXML function (really the `DictToString()` function does the bulk of the work but you get the point) will actually iterate through the outside and inside dictionary and output the following:


Jesse
Yesterday

This can be very useful for creating tidbits of xml to post to web actions. Sweet Huh?

Leave a Comment

36 thoughts on “FileMaker Dictionary Functions

  1. Hey, thanks for the article!

    Just for kicks I tried doing nested dicts, and it performed admirably:

    PassParameter(“first” ; “sam”) &
    PassParameter(“last” ; “barnum”) &
    PassParameter(“phones” ;
    PassParameter(“home” ; “415 965 0952”) &
    PassParameter(“cell” ; “(415) 997-3088”)
    )

    So you can really represent some complex types with this, if you’re determined. This could be kind of like JSON for FileMaker.

  2. @Sam – Yet again… the only one who like’s it. We love using dictionaries to pass things around it’s definitely allows you to write much, much cleaner code. Thanks for the comment!

  3. Hi Jesse,

    Thanks for the post!

    You got me thinking about dictionaries in FileMaker. I’m a big fan of Python, so I really wanted dictionaries in FileMaker.

    I took your dictionary format and used it to add auditing to a table. The basic idea is that each row in a table has a data_log value. That data_log is a running list of changes using your dictionary markup.

    I originally saw a neat demo on adding data logging to FileMaker (http://www.nightwing.com.au/FileMaker/demos8/demo809.html). That combined with your dictionary format allows me to write data changes to a data_log in the following format:

    I break the idea of a strict dictionary and allow multiple keys with the same value. I wrote functions to access the values by key name and position, e.g. GetNthTokenValue(dict, key, N), which finds the Nth occurrence of a key in a dictionary. So GetNthTokenValue(dict, “PrevValue”, 2) would return “a” in the above example.

    Since the data is in a regular format, it is straightforward to have a script populate a table with the values in the log dictionary for a record. So a user can click on a button and see a table with all the changes to a record. That change log might also be used to rollback to a previous state.

    If anyone is interested, I could try to get my example database together for people to take a look at.

    Thanks,

    Chris Wack

  4. Whoops,

    The web page removed my log example lines (probably due to the special formatting). So unmarked up they look like:

    Timestamp = 9/25/2007 5/:30/:59 PM UserName = Chris Wack FieldName = Notes PrevValue = b NewValue = c
    Timestamp = 9/25/2007 5/:30/:58 PM UserName = Chris Wack FieldName = Notes PrevValue = a NewValue = b
    Timestamp = 9/25/2007 5/:30/:56 PM UserName = Chris Wack FieldName = Notes PrevValue = NewValue = a

    Chris Wack

  5. Hi Jesse,
    great post, it has shown a new approach to FileMaker development, indeed!

    IMHO, I think that DictContains CF should actually read

    pattern = “

  6. @michele I think your comment was eaten by our fantastic comment system. Sometimes it will cause a comment to go a little haywire. If you could email me your comment I am definitely interested to hear the rest!

  7. @Jesse Probably I can’t see beyond the tip of my nose, but I wasn’t able to find your email address 🙁

    Anyway, I think that in functions like DictContains() and others, before you concatenate the parameter with the delimiters and do the search with Position(), you should run the input text through a CFEscapeText() function which would perform the same character substitutions as PassParameter().

    This way, it would match also dictionary keys that contain escaped sequences (which now are missed, it seems).

    Michele

  8. @Michele – You are 100% correct. Any dictionary keys that have characters that are escaped would be missed. I will make the changes that you suggested. Something along the lines of:

    escape#( value )

    substutite( value ; [“=”; “/=”]; [“:”; “/:”] ; [“>”; “/>”]; [“< " ; "/<"] ) and unescape#( value ) substitute( value; ["/="; "="]; [":"; "/:" ; [">“; “/>”]; [“<" ;"/<" ] ) Thanks a lot for the good eye.

  9. Fantastic stuff. Just beginning to develop in FMP9 having got by with 5 all these years, applescript, shell script and python adding functionality for us. Love FMP9. Am plotting on a sophisticated set up this time, and just thinking about underlying structures, relational structure, data logging, globals and passing of variables. Thus I came across this kind of stuff.

    And it seems fantastic to me. I want to use a consistent intelligible system across my scripting/development so its easily understood, repaired and extended by whoever at a later date. This I think answers that, where FMP has always been weak, though the Let a=1; b=2 and evaluate stuff had looked an option where multiple variables are required in script parameters, this seems a far more solid proposition to have as my standard variable handling technique.

    Love your work on it, thanks for writing about it and making it [your brain’s produce!] public. Good stuff.

    They say that for every comment you can read 100 parties who couldn’t be bothered, though amongst the esoteric developers who’ll be reading this I imagine more would comment appreciatively.

    Blessings,

    tom

    PS I am guessing you didn’t go the XML route in parsing format because you wouldn’t have been able to pass untouched HTML pages so simply? Though i may be wrong, not having yet implemented all your stuff… Are the custom functions stored in a file somewhere, or we just copy and paste.

    I’d be interested in Chris Wack’s data logging stuff too, rolling back on user error would be something kind of handy.

  10. @Thomas:

    Thanks for the supportive words. We really appreciate it, and we love to hear that this stuff is useful to folks out there.

    As to XML, Jesse may have a more elaborate answer (these are his work) but I believe the main reason is just that it would have been more complex and probably not appreciably better. I suppose you might argue that in some cases, have XML-compliant parameter blocks would be useful, but it simply wasn’t a design goal at the time.

    Interestingly, now that I think about it, it might be better. We sometimes need to interface Ruby automation scripts or Ruby on Rails web sites with parameterized scripts. To facilitate that, we have a Ruby class that generates parameter strings that are compatible with these functions, so we can easily pass multiple parameters to a script from an XML API call. If our parameters had been done as valid XML, this would have been much easier. So hmmm… Maybe a possibility for improvement down the road.

    Thanks so much,

    Geoff

  11. Cheers Geoff. Loved implementing your stuff. Oh, recursive functions with multiple parameters, no problemo now. Changed FMP power. Mildly rewrote some of the functions – implemented Michele’s nice suggestion of DictEscape & DictUnescape – which would potentially allow the rapid change to XML format – only one (well, two) place(s) to change…

    Am just playing about with FMP9 now, i figured i needed a file to help plan development for thegoodbook.co.uk’s systems. And that it would be good experience to build some sort of hierarchical documenting method to do so in FMP9. So done it. Good fun. Very underdeveloped, but will do the job. I have plans for it!! 😉 Anyway, if you want to see the implementation or borrow a pre-made simple hierarchy document structure file (only now suited to one hrchy) here it is: http://www.harmlesswise.com/download/wiki-basic.fp7

    I didn’t bother with DictReplace/DictAdd – just one DictSet – this seems to be fine by your logic…? Maybe there is a subtle distinction i hadn’t seen?

    Anyways many thanks for this stuff, i don’t think one could develop nearly as rapidly w/o such a system. FMP should include it in structure in future. Amongst other things!

    gratefully, t

  12. Just noticed an error in your DictFirst function. The definition is using “value” instead of “dict” as the input variable.

    Oh, and this blog is like totally awesome!

  13. @Raphael:

    Thank you for the correction, and for the compliment. I have fixed the article to use the “dict” function parameter correctly.

    Thanks,

    Geoff

  14. @Thomas:

    Thanks for the link. We’ll check it out. I agree that from a pure code standpoint, having a central function for escape and unescape would be better. But a design goal here was minimizing interdependencies. We copy these functions into existing files all the time, and since FileMaker still can’t copy/paste custom functions (grrr…) we wanted to make them as easy to move as possible.

    As it is, you can get the meat of things with just #, #P and #R, which is only three bounces in to/out of the custom function dialog box. If we ever get the power to copy/paste CFs easily, we’ll almost certainly make these smarter along those lines.

    As for DictAdd, I completely agree. I never use it because it is easy to just append using # instead. DictReplace is special though. It takes care to *remove* the existing value before appending, so you can change a value from one thing to another. This isn’t a super-common need, but I’ve found it useful a time or two.

    I just realized, by the way, that I’m using our names for these functions and not the names in the article. For the sake of general interest, I’ll leave them there. In our systems, we use these names:

    * DictSet is called #
    * GetParameter is called #P

    This just saves loads of typing since we use these functions in almost every script call. So to pass a “name” of “geoff” to a script, the param looks like this:

    #("name", "geoff")

    And to get that param in a script we do:

    #P("name")

    It looks a little crazy at first, but once you get used to it, it is nicely concise. I think when Jesse wrote this article he didn't want to foist our wacky symbols on the world.

    Anyway, all good stuff. At this point I'm rambling again 🙂

    Geoff

  15. @Chris – done now, so don’t worry (and re-uploaded wiki-basic for those interested, btw got nice Apple theme…). One custom function for the ‘Log field’ to cover all manual entry – very nice. Liked the nightwing stuff. Scuse my ecletic hobbies there for all to see… and the Christian fundamentalist at play

    @Geoff – yes indeed # #P are the way to go, even speedier… ! love it! 🙂 – i’m so glad to have the time to look around and clock these methods b4 even beginning to build my system [the wiki-basic is just a test db/documenting tool]

    blessings, t

  16. & i reckon you did because for simple PatternCount, Position and such two characters are MUCH easier to find. Do a Position(“<” etc and it would find it in all the escaped sections too: “\<name…”. But find “<:” and you can be guaranteed they are the markers/delimiters as the others will have been escaped “\<\:”. So i think i agree with u guys, it is simplest and fastest to be <: or some such combination of two chars.

  17. Geoff & Jesse

    I love all these dictionary Cfs but it seems to me that the formula for DictFirst() is unnecessariiy complicated. Of course I may be missing something, but the following seems to do what’s necessary:

    Let (
    nameLength = Position ( dict ; “:=” ; 3 ; 1 ) – 3 ;
    If ( Left ( dict ; 2 ) = “<:" ; Middle ( dict ; 3 ; nameLength ) ) ) Having said that, this is all awesome stuff cheers Tom

  18. @Tom –

    I’m glad you enjoyed our stuff!

    Your function works fine in the vast majority of cases and quite frankly is ridiculously cleaner. The only problem would be if there were gibberish in front of the dictionary information like this:

    gibberish<:first_key:= Your function wouldn't be able to figure out the first key. It's probably a safe assumption that this is an outlier or even that this shouldn't be a valid dictionary. I figured I would cover the case if I could.

  19. Jesse

    I guess I took the view that “gibberish<: …” is not a valid dictionary but, yes, I can see that it could be viewed otherwise.

    cheers

    Tom

  20. i use a #K and #V for cycling thru passed arrays in manner of php for each ARRAY as Key=>Value…

    #K [ dict ; number ] — short for #Key to obtain the key for a dictionary at a given position, starting from 1 (not zero as with PHP)

    Let(
     [
      startValue = "<:";  // for #V change to ":="  (#V short for #Value)
      endValue = ":=";  // for #V change to ":>"
      positionOfStartValue= Position( dict ; startValue ; 1 ; number );
      endOfStartValue= If( positionOfStartValue > 0 ; positionOfStartValue  + Length(startvalue); -1 );
      beginningOfEndValue= Position( dict; endValue; endOfStartValue; 1 );
      found = If( beginningOfEndValue > -1 and endOfStartValue > -1 ; True; False );
      lengthFoundValue = beginningOfEndValue - endOfStartValue;
     foundValue = Middle( dict; endOfStartValue ; lengthFoundValue )
    ];
    
     If( found ; foundValue ; "" )
    
    )

    Of course, you could also do a quick #C [ dict ] function:

    PatternCount ( dict ; "<:" )

    Sorry if this is ‘spamming’ your site, but I use these functions *all* the time now! Blessings, t

  21. I suggest you change the long name of PassParamter to DictItem. This will help group all your Dict definitions together. Probably too late now for some.

    Also, not sure I have seen this function:

    DictFromFields( theFields )
    /*
    Example
    DictFromFields ( fieldNames( get(FileName); get(LayoutName)))
    */
    If (
    IsEmpty ( theFields );
    theFields;

    Let ([
    name = GetValue ( theFields ; 1 );
    value = GetField ( name)
    ] ;

    DictItem( name ; value ) &

    DictFromFields (
    RightValues ( theFields ; ValueCount ( theFields ) – 1 )
    )
    )
    )

  22. That didn’t work! I’m sure I entered it right but anyway you’re missing the trailing slash for the DictToXML definition.

  23. Thanks for these custom functions! They blew my mind! Check out the link to see how I used them to implement a “Row Highlight Selection” feature, capable of the following:

    1) The active record is automatically highlighted when you access a record
    2) Ctrl + click on a record to highlight more than one record at a time
    3) Shift + click to highlight all records between the currently active record and the one you click on
    4) Click the “View Highlighted” button to view only the highlighted records in a new window
    5) now open a new window and do all of the above again – each window has it’s own set of highlighted records!

  24. Seems like this is a pseudo-JSON; but why not just go all the way and actually use JSON? Then you can use the results in web viewers, etc.

  25. Oright my loves, gone the next level, taken the ball and run with it to deep dark places. Full syntactic dictionary and list functionality is what i was after and i reckon i’ve pretty much got there, effective Foreach functionality and much much more….

    Enjoy, but i guess it’s only for the lunatic fringe, btw as per Bruce R’s comments, it produces JSON though can’t read it alas… (i didn’t start with trying to get a JSON reader and my mind nearly split doing the above anyway…)>>>

    http://www.harmlesswise.com/0906-filemaker-python-dictionary-list-functions

    T

  26. just got a JSON reader up and running, tested fairly extensively, version 10 now working. It was actually quite simple and very fast compared to the other stuff! It just flies through JSON and replaces syntax with appropriately escaped Dict/List syntax. Not yet made use of it, but hopefully by this time next week our web menu system will be powered by it!

    Here is link to key changed function: http://www.harmlesswise.com/0906-filemaker-python-dictionary-list-functions#1.1.1

    But you’ll need to see version notes at end and update 2 other functions, minor speed/flexibility improvements etc.

    T x

  27. Based on a script snipped posted to the Pause on Error (Portland 2010) wiki by John Sindelar, I have created a new function to work with your dictionary functions. This function converts a dictionary to local variables.

    
    //DictConvertToParameters ( dict ) 
    //Created by Don Levan. Adapted from the "Seed Code Calendar: Calendar Utility: Parameters to Local Variables" script posted by John Sindelar to the Pause on Error, Portaland 2010 wiki. 
    //Purpose: Converts a dict to local variables. 
    
    Let ( 
      [ 
    
        n = dict ; 
        prefix = "$" ; 
    
    
        e = PatternCount ( n ; " = " ) = 0 ; 
    
        string = Substitute ( 
                              n ; 
                              [":><:"; "; "];
                              ["" ; "" ] 
    
                            ) 
    
      ] ; 
    
      Evaluate ( "Let ( [ " & pre?x &  string & "\" ] ; \"\" )" ) 
    
    )
    

    I am hoping you all can help me identify escapes I might have missed that would cause it to fail.

  28. I just found an error with DictRemove and DictFirst functions. Neither of them use substitute on the ‘name’. If use use the DictToString() function with DictRemove and DictFirst as they are, you would get stuck in an endless loop (until FM gives you an out of memory error).

    Example of DictToString() that will cause an endless loop:
    DictToString( #(“Table::Field”;”value”) ; “$name: $value” )

    setting the corresponding ‘Let’ variables to the calculation below fixes the problem:

    DictRemove
    pattern = “”; “/>”]; [“<" ; "/”; “>”]; [“/<" ; "<" ] )

  29. oops, looks like the last part of my message was cut off, let me try it again…

    DictRemove
    pattern = ""; "/>"]; ["<" ; "/<"] ) & ":=" ;

    DictFirst
    foundValue = Substitute( Middle( dict; endOfStartValue ; lengthFoundValue ) ; ["/:"; ":"]; ["/="; "="]; ["/>"; ">"]; ["/<" ; "<" ] )

  30. @Don: This is only about a year late, but I wanted a reliable way of initializing all names as local variables and I just saw your function. When any value in the dictionary contains quotes, using an Evaluate() function will fail unless you first escape the quotes. Then it turns into a recursive mess to set variables back to their properly unescaped value(s). I suspect that it’s easier to do this with SFR’s existing functions recursively.

    Here’s my stab at it:

    // Name: DictInit( dict )
    // Purpose: initialize all names in dictionary as local variables matching values

    Let( [
    thisName = DictFirst ( dict )
    ] ;

    If(
    not IsEmpty( thisName ) ;
    Let(
    [
    thisValue = Quote( Substitute ( DictGet ( dict ; thisName ) ; [ “\”” ; “%34” ] ) ) ; // escape quotes in value
    dict = DictRemove ( dict ; thisName ) // pop this off the top of the dictionary
    ] ;
    Evaluate( “Let( $” & thisName & ” = ” & thisValue & ” ; \”\” )” ) // assign the escaped value to the variable
    & Evaluate( “Let( $” & thisName & ” = Substitute ( $” & thisName & ” ; \”%34\” ; \”\\\”\” ) ; \”\” )” ) // reverse escaping
    & DictInit ( dict ) // continue until all variables are initialized
    )
    )
    )

  31. Thanks very much for this. I love dictionaries in PHP, REALbasic and other languages, and was hoping I would have to write FileMaker dictionary routines myself. I did add one function to the library which you might be interested in: DictKeys.

    Let(
    [
    FirstItem = DictFirst( Dict )
    ];

    Case(
    Dict = Null;
    Null;

    FirstItem & ¶ & DictKeys( DictRemove( Dict; FirstItem ) )
    )
    )

    Null is a custom function that just returns an empty string. Using this with the Pop function I found here allows very easy iteration through a dictionary’s entries.