Problem with var and records in a property

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

Hi @commiepinko. Welcome to MacScripter.

Thanks for posting. It’s been fun re-reading this topic. :grin:

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

@Nigel_Garvey Hi Nigel, two notes:

  1. 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.

  2. 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. :slightly_smiling_face:

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

1 Like

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.

Thanks much.

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