Creating KM dictionaries from Scripts

Whereas passing KM8’s new dictionaries to scripts is very useful, the reverse traffic (building and returning dictionaries from scripts) seems likely to be less busy.

Not, however, impossible, though I think I would often use JSON.stringify() and JSON.parse() for that return direction.

Code snippets below for converting JavaScript dictionaries (Sierra onwards ES6) and Applescript records to Keyboard Maestro dictionaries:


(() => {
    'use strict';

    // JS DICTIONARY -> KEYBOARD MAESTRO DICTIONARY --------------------------

        // kmDictFromJSDict :: String -> Dict -> KM Dictionary
        const kmDictFromJSDict = (strName, dct) => {
            const kme = Application('Keyboard Maestro Engine');
            return (
                // Any existing KM dictionary of the given name cleared,
                elem(strName, &&

                // and a new KM dictionary of the given name built
                // from the JS dictionary
                .reduce((a, k) => kmDictValueSet(strName, k, dct[k]), []),

        // kmDictValueSet :: String -> String -> String -> KM ()
        const kmDictValueSet = (strDict, strKey, strValue) =>
            Application('Keyboard Maestro Engine')
                    MacroActionType: 'SetDictionaryValue',
                    Dictionary: strDict,
                    Key: strKey,
                    Value: strValue

        // GENERIC FUNCTIONS -----------------------------------------------------

        // elem :: Eq a => a -> [a] -> Bool
        const elem = (x, xs) => xs.indexOf(x) !== -1;

        // plistMay :: JS Object -> Maybe Plist String
        const plistMay = jso => {
            var error = $();
            const maybeString = $.NSString.alloc.initWithDataEncoding(
            return Boolean(error.code) ? {
                nothing: true,
                msg: error.localizedDescription
            } : {
                nothing: false,
                just: ObjC.unwrap(maybeString)

        // TEST ------------------------------------------------------------------
        return kmDictFromJSDict('Matriarchs', {
                Sarah: 'Abraham',
                Rebecca: 'Isaac',
                Rachel: 'Jacob',
                Leah: 'Jacob'


use framework "Foundation"


-- kmDictFromRecord :: String -> Record -> KM Dictionary
on kmDictFromRecord(strName, rec)
    tell application "Keyboard Maestro Engine"
        -- Any existing dictionary of the given name cleared,
        if exists dictionary strName then delete dictionary strName
        -- and new dictionary of the given name built
        script kvAdded
            -- kmDictValueSet :: String -> String -> String -> KM Dictionary
            on kmDictValueSet(strDict, strKey, strValue)
                tell application "Keyboard Maestro Engine"
                    do script (my recordAsPlist({{|MacroActionType|:¬
                        "SetDictionaryValue", |Dictionary|:¬
                        strDict, |Key|:strKey, |Value|:strValue}}))
                    dictionary strDict
                end tell
            end kmDictValueSet
            on |λ|(a, k)
                my kmDictValueSet(strName, k, just of keyValue(k, rec))
            end |λ|
        end script
        my foldl(kvAdded, missing value, my keys(rec))
    end tell
end kmDictFromRecord

-- TEST ----------------------------------------------------------------------
on run
    tell application "Keyboard Maestro Engine"
        dictionary keys of my kmDictFromRecord("Matriarchs", ¬
            {Sarah:"Abraham", Rivka:"Isaac", Rachel:"Jacob", Leah:"Jacob"})
    end tell
end run

-- GENERIC FUNCTIONS ---------------------------------------------------------

-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from 1 to lng
            set v to |λ|(v, item i of xs, i, xs)
        end repeat
        return v
    end tell
end foldl

-- keys :: Record -> [String]
on keys(rec)
    (current application's NSDictionary's dictionaryWithDictionary:rec)'s allKeys() as list
end keys

-- keyValue :: String -> Record -> Maybe a
on keyValue(strKey, rec)
    set ca to current application
    set v to (ca's NSDictionary's dictionaryWithDictionary:rec)'s objectForKey:strKey
    if v is missing value then
        {nothing:false, just:item 1 of ((ca's NSArray's arrayWithObject:v) as list)}
    end if
end keyValue

-- Lift 2nd class handler function into 1st class script wrapper 
-- mReturn :: Handler -> Script
on mReturn(f)
    if class of f is script then
            property |λ| : f
        end script
    end if
end mReturn

-- recordAsPlist :: Record -> Maybe Plist String
on recordAsPlist(rec)
    set ca to current application
    (ca's NSString's alloc()'s ¬
        initWithData:(ca's NSPropertyListSerialization's ¬
            dataWithPropertyList:rec format:(ca's NSPropertyListXMLFormat_v1_0) ¬
                options:0 |error|:(missing value)) encoding:(ca's NSUTF8StringEncoding)) as string
end recordAsPlist

Agreed on both counts. But sometimes, perhaps for others, we need the reverse traffic.
Thanks for sharing.