Decades after the fact… It’s possible to use the Foundation framework to do an end run around AppleScript’s limitation.
use framework "Foundation"
set preferredShades to {blue:"azure", red:"scarlet", yellow:"jaundice"}
set theColor to "blue"
set preferredBlue to Get_Value from preferredShades for theColor
to Get_Value from aRecord for labelVariable
set recordData to my (NSDictionary's dictionaryWithDictionary:aRecord)
set recordDataKeys to recordData's allKeys()
if (recordDataKeys as list) contains labelVariable then
set itemValue to recordData's valueForKey:labelVariable
return itemValue as text
else
return missing value
end if
end Get_Value
Thanks for posting. It’s been fun re-reading this topic.
valueForKey: actually returns missing value anyway if the key isn’t in the target dictionary, so the code could be simplified to:
use framework "Foundation"
set preferredShades to {blue:"azure", red:"scarlet", yellow:"jaundice"}
set theColor to "blue"
set preferredBlue to Get_Value from preferredShades for theColor
to Get_Value from aRecord for labelVariable
set recordData to current application's NSDictionary's dictionaryWithDictionary:aRecord
set itemValue to recordData's valueForKey:labelVariable
if (itemValue is missing value) then return itemValue
return itemValue as text
end Get_Value
However, this too assumes that all the values in the record will be text and that none will be missing value. The first assumption can be got round by putting the ObjC value into an NSArray and coercing that to list. The only way I can think of immediately around the second assumption is to pass one’s own “not found” value, which should be one known not to be in the record.
use framework "Foundation"
set preferredShades to {blue:"azure", red:"scarlet", yellow:"jaundice", green:{g:17}, purple:missing value}
set theColor to "black"
set preferredBlue to Get_Value from preferredShades for theColor given notFound:Saturday
to Get_Value from aRecord for labelVariable given notFound:notFound
set recordData to my (NSDictionary's dictionaryWithDictionary:aRecord)
if ((recordData's allKeys())'s containsObject:labelVariable) then
set itemValue to recordData's valueForKey:labelVariable
set itemArray to current application's NSArray's arrayWithObject:itemValue
return (itemArray as list)'s beginning
else
return notFound
end if
end Get_Value
valueForKey is a special API related to Key-Value Coding (KVC) which is not needed in this case.
The standard API to get a value from a dictionary is objectForKey.
The Objective-C nil is bridged to missing value so the allKeys detour is not needed either.
use framework "Foundation"
set preferredShades to {blue:"azure", red:"scarlet", yellow:"jaundice", green:{g:17}, purple:missing value}
set theColor to "black"
set preferredBlue to getValue from preferredShades for theColor given notFound:Saturday
to getValue from aRecord for labelVariable given notFound:notFound
set recordData to my (NSDictionary's dictionaryWithDictionary:aRecord)
set itemValue to recordData's objectForKey:labelVariable
if itemValue is missing value then
return notFound
else
set itemArray to current application's NSArray's arrayWithObject:itemValue
return (itemArray as list)'s beginning
end if
end getValue
By the way the NSArray coercion to get heterogenous types is pretty nifty.
Hi Stefan. Thanks. I usually think of valueForKey: and objectForKey: as being interchangeable where the keys are text. But only objectForKey: can handle keys that are other kinds of object. Since the keys from an AppleScript list can only be rendered as text in an NSDictionary, I was thinking of objectForKey: as not being needed here. I guess it’s a matter of perception — unless one happens to me more efficient than the other?
I was trying to make the point above that missing value is also a possible value for a record property. If objectForKey: returns missing value, it can mean either that there’s no property with that key or that there is one and its value is missing value. This is why I kept allKeys. Another alternative would be:
use framework "Foundation"
set preferredShades to {blue:"azure", red:"scarlet", yellow:"jaundice", green:{g:17}, purple:missing value}
set theColor to "black"
set preferredBlue to Get_Value from preferredShades for theColor given notFound:Saturday
to Get_Value from aRecord for labelVariable given notFound:notFound
set recordData to my (NSDictionary's dictionaryWithDictionary:aRecord)
return ((recordData's objectsForKeys:{labelVariable} notFoundMarker:notFound) as list)'s beginning
end Get_Value
But this would require the user to know not only a value that’s not in the record, to be returned if the key’s not found, but that this value mustn’t be missing value because it would cause objectsForKeys:notFoundMarker: to error.
I think I got it from Shane, either from his book or in one of these fora.
valueForKey is more expensive. It can be applied to an array of dictionaries to get an array of all values or to get maximum, minimum, average and the result of some other collection operators of all values for the given key. To just get a single value for a key objectForKey – or in real Objective-C key subscription array[@"key"] – is preferable.
My bad, I missed the point that missing value does not necessarily become nil and NSDictionary values cannot be nil. It will be bridged to NSNull
Not being fluent in Obj-C, some of this is rather above my pay grade, but I’m charmed by the sudden burst of enthusiasm for my flawed solution to an ancient (in computer years) issue.
In any case, what you’ve provided is actually quite useful in my current project, eliminating dozens of lines of kludgy fussing. I’m now puzzling over how to do the opposite: create a record using variables for its labels.
This thread from 2003 reminded me of how proud I was to discover a way to mimic the NSDictionary behavior in AppleScript itself. This was in the AppleScript Studio days way before ASOC.
ASS had an entity called “data source” that consisted of rows and columns and was mostly intended as a data source for tables.
But at some point I realized I could use this “data source” just like a dictionary by assigning the “name” property to rows. Then I could set/modify/retrieve the desired data source values by using this “name” just like a key in a dictionary.
Well I’m sure I wasn’t the only one who discovered it back then anyway…
If you already have keys and values with which to start, you can create a record like this:
use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
-- Lists of keys and values in corresponding order.
set keys to {"blue", "red", "yellow", "green", "purple"}
set values to {"azure", "scarlet", "jaundice", {g:17}, missing value}
set dictionary to current application's NSDictionary's dictionaryWithObjects:values forKeys:keys
set myRecord to dictionary as record
To add a new property or change an existing one’s value:
use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
set myRecord to {blue:"azure", red:"scarlet", yellow:"jaundice", green:{g:17}, purple:missing value}
set theKey to "white"
set theValue to "blinding"
set dictionary to current application's NSMutableDictionary's dictionaryWithDictionary:myRecord
if (theValue is missing value) then set theValue to current application's NSNull's |null|()
dictionary's setObject:theValue forKey:theKey
set myRecord to dictionary as record
To remove a property or properties:
use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
set myRecord to {blue:"azure", red:"scarlet", yellow:"jaundice", green:{g:17}, purple:missing value}
set keyToRemove to "red"
set dictionary to current application's NSMutableDictionary's dictionaryWithDictionary:myRecord
dictionary's removeObjectForKey:keyToRemove
set myRecord to dictionary as record
-- Or:
set myRecord to {blue:"azure", red:"scarlet", yellow:"jaundice", green:{g:17}, purple:missing value}
set keysToRemove to {"green", "red"} -- The order doesn't matter
set dictionary to current application's NSMutableDictionary's dictionaryWithDictionary:myRecord
dictionary's removeObjectsForKeys:keysToRemove
set myRecord to dictionary as record