How to enumerate the keys and values of a record in AppleScript
Asked Answered
L

4

5

When I use AppleScript to get the properties of an object, a record is returned.

tell application "iPhoto"
    properties of album 1
end tell

==> {id:6.442450942E+9, url:"", name:"Events", class:album, type:smart album, parent:missing value, children:{}}

How can I iterate over the key/value pairs of the returned record so that I don't have to know exactly what keys are in the record?

To clarify the question, I need to enumerate the keys and values because I'd like to write a generic AppleScript routine to convert records and lists into JSON which can then be output by the script.

Lucaslucca answered 5/8, 2013 at 15:56 Comment(3)
If I understand the question, it sounds like what you want to do is 1) get a list of just the keys, 2) make a decision about which key you are interested in and assign a variable to the name of that key, then 3) look up the value corresponding to that key. According to this there is no easy way to do this with applescript, though some hacks may be possibleNorean
AppleScript records are fixed sets of properties mostly analogous to C structs, not arbitrary key-value collections like Perl hashes or Python dicts. Since AppleScript lacks introspection, there's no built-in way to extract a list of property names. There are various hacks and kludges you can use, but they're all nasty and/or unreliable. As others have said, it's not clear why you wish to do this, rather than use the language as it's designed to be used.Tareyn
@Tareyn I think your comment is, unfortunately, the answer I was looking for. However, I can't accept a comment as an answer. Would you mind posting the comment as an answer so that others will benefit from it being the accepted answer?Lucaslucca
S
9

I know it's an old Q but there are possibilities to access the keys and the values now (10.9+). In 10.9 you need to use Scripting libraries to make this run, in 10.10 you can use the code right inside the Script Editor:

use framework "Foundation"
set testRecord to {a:"aaa", b:"bbb", c:"ccc"}

set objCDictionary to current application's NSDictionary's dictionaryWithDictionary:testRecord
set allKeys to objCDictionary's allKeys()

repeat with theKey in allKeys
    log theKey as text
    log (objCDictionary's valueForKey:theKey) as text
end repeat

This is no hack or workaround. It just uses the "new" ability to access Objective-C-Objects from AppleScript. Found this Q during searching for other topics and couldn't resist to answer ;-)

Update to deliver JSON functionality: Of course we can dive deeper into the Foundation classes and use the NSJSONSerialization object:

use framework "Foundation"
set testRecord to {a:"aaa", b:"bbb", c:"ccc"}

set objCDictionary to current application's NSDictionary's dictionaryWithDictionary:testRecord

set {jsonDictionary, anError} to current application's NSJSONSerialization's dataWithJSONObject:objCDictionary options:(current application's NSJSONWritingPrettyPrinted) |error|:(reference)

if jsonDictionary is missing value then
    log "An error occured: " & anError as text
else
    log (current application's NSString's alloc()'s initWithData:jsonDictionary encoding:(current application's NSUTF8StringEncoding)) as text
end if

Have fun, Michael / Hamburg

Sniff answered 9/4, 2015 at 18:12 Comment(5)
This is exactly what I thought of. But did you actually try it with a get properties of ... rather than a constructed one.Renal
Sorry for the late answer: Shane Stanley himself says "The scripting bridge cannot handle such record entries. (And if you understand what is involved behind the scenes, you might have some sympathy for the scripting bridge.)" – I believe him :-DSniff
@Sniff Great snippet of code. I implemented the top snippet to iterate through the keys and would like to know how to delete the key-value pair where theKey as text = "b" (example). Tried objCDictionary's removeObjectForKey:"b" without any luck. Would you be able to help, perhaps by extending your answer to include this extra line? Much appreciated.Incinerator
You have to create a mutable dictionary to use removeObjectForKey: set objCDictionary to current application's NSMutableDictionary's dictionaryWithDictionary:testRecord - Cheers!Sniff
When I try this on a reminder record returned from the Reminders app, I get a "Script Error: Can’t make list id "CCB198D9-4122-4161-9FA5-68D61E91919F" of application "Reminders" into the expected type. Any idea how to address that other than @Chris N's solution (which I've had success with)?Rosemaria
G
3

If you just want to iterate through the values of the record, you could do something like this:

tell application "iPhoto"
    repeat with value in (properties of album 1) as list
        log value
    end repeat
end tell

But it's not very clear to me what you really want to achieve.

Grogshop answered 5/8, 2013 at 17:4 Comment(1)
I believe they want basically this, but also including the keys associated with each value.Sike
A
1

Basically, what AtomicToothbrush and foo said. AppleScript records are more like C structs, with a known list of labels, than like an associative array, with arbitrary keys, and there is no (decent) in-language way to introspect the labels on a record. (And even if there were, you’d still have the problem of applying them to get values.)

In most cases, the answer is “use an associative array library instead.” However, you’re specifically interested in the labels from a properties value, which means we need a hack. The usual one is to force an error using the record, and then parse the error message, something like this:

set x to {a:1, b:2}
try
    myRecord as string
on error message e
    -- e will be the string “Can’t make {a:1, b:2} into type string”
end

Parsing this, and especially parsing this while allowing for non-English locales, is left as an exercise for the reader.

Aland answered 5/8, 2013 at 23:46 Comment(0)
L
0

ShooTerKo's answer is incredibly helpful to me.

I'll bring up another possibility I'm surprised I didn't see anyone else mention, though. I have to go between AppleScript and JSON a lot in my scripts, and if you can install software on the computers that need to run the script, then I highly recommend JSONHelper to basically make the whole problem go away:

EDIT: fix link for JSONhelper: https://www.mousedown.net/software/JSONHelper.html

Languor answered 9/4, 2015 at 19:19 Comment(4)
This app is really cool and it is available in the Mac App Store, too. It gets automatically updated then ;-)Sniff
It tells me "Unable to add the item because it is not scriptable." when I try to add that JSONHelper app to the applescript command Library.Devondevona
luckydonald - Not sure what you mean by "try to add that JSONHelper app to the applescript command Library." You just install the application, then address it through Applescript with: tell application "JSON Helper" set <applescriptRecordVariable> to fetch json from <jsonVariable> end tell Or whatever. To see it's commands, open it's dictionary in Script Editor.Languor
The isair/JSONHelper repo seems to be a library, not an app; I don't see how it helps. As for the App Store, there are 2 "JSONHelper", none of them by the dev of the repo, and none of them scriptable.Brei

© 2022 - 2024 — McMap. All rights reserved.