Listing record keys in 'Execute an AppleScript' actions

A small note about AppleScripts – may be new to some (was to me :slight_smile:)

I'm writing more JavaScript than AppleScript these days, but one of the basic structural constraints
of the AppleScript record type was always that you couldn't just ask a record for a list of its keys.
( In jargon terms, AppleScript records lack 'introspection' )

(Various kludges were possible – I used to use a slow and silly clipboard trick)

I noticed today, however, for the first time, (others probably saw it long ago)
that this occasionally exasperating limit has effectively been breached, since Yosemite,
by AppleScript's now improved access to ObjC library methods.

If we start a script with

use framework "Foundation"

We can now write things like:

use framework "Foundation"

on keysOfRecord(rec)
    set ca to current application
    return (ca's NSDictionary's dictionaryWithDictionary:rec)'s allKeys() as list
end keysOfRecord

keysOfRecord({|name|:"nemo", address:"Tokyo", |language|:"Latin", vehicle:"tandem"})

A bit late for me ...

The owl of Minerva flies out at dusk

etc ... but maybe useful to someone ?

1 Like

Thanks, Rob. That's great! :thumbsup:

Can you help us with the next step: Getting the value from the record when the key is in a variable:

set myRec to {|name|:"nemo", address:"Tokyo", language:"Latin", vehicle:"tandem"}

set myKey to "address"

address of myRec ## This works

myKey of myRec ## FAILS with Can’t get myKey of . . .

Of course, we'd like to do something like this:

--- GET VALUES FOR ALL KEYS IN RECORD ---

set keyList to keysOfRecord(myRec)

repeat with oKey in keyList
  set keyStr to text of oKey
  set valueStr to keyStr of myRec   ## FAILS
  log (keyStr & ": " & valueStr)
end repeat

The next step is probably to switch to JavaScript, in which querying a dictionary is rather simpler:

JavaScript:

function run() {

    var rec = {
        name: "nemo",
        address: "Tokyo",
        language: "latin",
        vehicle: "tandem",
        profession: "nudnik"
    }

    return {
        // List of keys in the record
        keys: Object.keys(rec),

        // List of values in the record
        values: Object.keys(rec)
            .map(function (key) {
                return rec[key];
            }),

        // Each key paired with its value
        pairs: Object.keys(rec)
            .map(function (key) {
                return [key, rec[key]];
            })
    }
}

AppleScript (skipping the details of testing the class of the ObjC value, and handling accordingly)

We could write code like that below, but it's still clumsy. Chris may have more elegant suggestions for cracking this one.

use framework "Foundation"

on run {}
    set rec to {|name|:"nemo", address:"Tokyo", |language|:1, vehicle:"tandem", profession:"nudnik"}
    
    set lstKeys to keysOfRecord(rec)
    
    return {keys:lstKeys, values:map(mClosure(justValue, {rec:rec}), lstKeys), pairs:map(mClosure(keyValPair, {rec:rec}), lstKeys)} ¬
        
end run

on keysOfRecord(rec)
    set ca to current application
    return (ca's NSDictionary's dictionaryWithDictionary:rec)'s allKeys() as list
end keysOfRecord

on maybeRecordValue(rec, strKey)
    set ca to current application
    set v to ((ca's NSDictionary's dictionaryWithDictionary:rec)'s objectForKey:strKey)
    
    -- simplest case
    return v as string
end maybeRecordValue

on justValue(strKey)
    maybeRecordValue(my closure's rec, strKey)
end justValue

on keyValPair(strKey)
    {strKey, maybeRecordValue(my closure's rec, strKey)}
end keyValPair


-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    set mf to mReturn(f)
    set lng to length of xs
    set lst to {}
    repeat with i from 1 to lng
        set end of lst to mf's lambda(item i of xs, i, xs)
    end repeat
    return lst
end map

-- Handler -> Record -> Script
on mClosure(f, recBindings)
    script
        property closure : recBindings
        property lambda : f
    end script
end mClosure

-- Lift 2nd class function into 1st class wrapper 
-- handler function --> first class script object
on mReturn(f)
    if class of f is script then return f
    script
        property lambda : f
    end script
end mReturn

A post was split to a new topic: Making AppleScript Records Dynamic using ASObjc

My reply, with complete script, has been moved to a new topic: