Of the three major Cocoa collection classes, NSArray (and its subclasses), NSDictionary (and its subclasses), and NSSet (and its subclasses), only the first two are readily converted to Applescript values with the “as” operator:
set || to current application
set myArray to ||'s NSArray's arrayWithArray:{||'s NSNumber's numberWithInt:1, ||'s NSString's stringWithString:"two", ||'s NSArray's arrayWithArray:{||'s NSNumber's numberWithInt:3, ||'s NSArray's arrayWithArray:{||'s NSNumber's numberWithInt:4, ||'s NSNumber's numberWithInt:5}}}
set myList to myArray as list --> the Applescript list {1, "two", {3, {4, 5}}}, each item of which, including nested list items, is an Applescript value
set myDictionary to ||'s NSDictionary's dictionaryWithDictionary:{a:||'s NSNumber's numberWithInt:1, b:||'s NSString's stringWithString:"two", c:||'s NSDictionary's dictionaryWithDictionary:{d:||'s NSNumber's numberWithInt:3, e:||'s NSNumber's numberWithInt:4, f:||'s NSNumber's numberWithInt:5}}
set myRecord to myDictionary as record --> the Applescript record {a:1, b:"two", c:{d:3, e:{f:4, g:5}}}, each property value of which, including nested property values, is an Applescript value
However, the same does not hold for the conversion of an NSSet to what would seem to be its natural Applescript counterpart, namely a list:
set mySet to ||'s NSSet's setWithArray:{1, "two", 3.3}
set myList to mySet as list --> Applescript list of length 1, whose sole item is the Cocoa NSSet object mySet
It is straightforward to convert an individual NSSet object to an Applescript list by first converting it to an NSArray and then converting the NSArray to an Applescript list:
set mySet to ||'s NSSet's setWithArray:{1, "two", 3.3}
set myArray to mySet's allObjects()
set myList to myArray as list --> Applescript list of length 3, whose items are the Applescript values 1, "two", and 3.3 in an undefined order
However, this approach can’t be applied globally to NSSet objects nested within other Cocoa collections (NSArray, NSDictionary, and NSSet) or Applescript lists or records without applying the conversion technique to each NSSet object individually.
The cocoaToASValue handler described below is an ASObjcC handler that attempts to overcome this missing functionality. It is a general purpose Cocoa object-to-Applescript value converter that converts NSSet (or NSSet subclass) objects to Applescript lists, including NSSet objects nested within Cocoa collections (NSArray, NSDictionary, and NSSet objects) and Applescript lists and records.
For those not accustomed to using NSSet objects, a couple of things to keep in mind concerning the Applescript lists to which they are converted:
(1) Since NSSet objects are unordered, the order of the Applescript list items after conversion will be undefined
(2) (This actually pertains to the conversion of an Applescript list to an NSSet, not the conversion of an NSSet to an Applescript list for which the current handler is designed. But I still think it’s worth mentioning in this discussion of NSSet objects and Applescript lists.) Since NSSet objects (apart from NSCountedSet objects) do not allow duplicate values, any items that are distinct from Applescript’s viewpoint but identical from Cocoa’s viewpoint will be reduced to a single value in the NSSet as well as the Applescript list following conversion. For example, each of the following NSSet instantiation commands will result in a single-item NSSet object and consequently a single-item Applescript list after conversion:
(current application's NSSet)'s setWithArray:{1, true} --> the Applescript list {1} after conversion
(current application's NSSet)'s setWithArray:{0, false} --> the Applescript list {0} after conversion.
Example of handler usage:
set || to current application
set cocoaArray to (||'s NSArray)'s arrayWithArray:{1, 2.2, "three"}
set cocoaDictionary to (||'s NSDictionary)'s dictionaryWithDictionary:{a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}
set cocoaSet to (||'s NSSet)'s setWithArray:{10, 11.11, "twelve"}
set cocoaArrayWithNestedObjects to (||'s NSArray)'s arrayWithArray:{cocoaArray, cocoaDictionary, cocoaSet, {cocoaArray, cocoaDictionary, cocoaSet, {cocoaArray, cocoaDictionary, cocoaSet, {cocoaArray, cocoaDictionary, cocoaSet}}}}
my cocoaToASValue(cocoaArrayWithNestedObjects)
--> {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}, {{1, 2.200000047684, "three"}, {a:false, b:{4, 5, 6}, c:{aa:7, bb:8, cc:9}}, {11.109999656677, 10, "twelve"}}}}}
--> note the apparent alteration in the order of NSSet items following conversion to Applescript lists
Handler:
use framework "Foundation"
property || : current application
on cocoaToASValue(theObj)
-- Converts a Cocoa object to a corresponding Applescript value
-- If it cannot be converted, it will remain unchanged as a Cocoa object
tell theObj
-- Perform a preliminary conversion of the input argument to an Applescript value
-- If the input argument is a collection, extract its item values into an Applescript list
set dictionaryKeys to null
try
-- Handle the case where the input argument is a Cocoa object
-- Note that if the input argument is instead an Applescript value, it will trigger an error when it encounters the isKindOfClass method call and will be processed in the "on error" clause
if (its isKindOfClass:((my ||'s NSArray)'s |class|())) as boolean then
-- If the input argument is a Cocoa NSArray or one of its subclasses, convert it to an Applescript list
set asValue to it as list
else if (its isKindOfClass:((my ||'s NSSet)'s |class|())) as boolean then
-- If the input argument is a Cocoa NSSet or one of its subclasses, first convert it to an NSArray, then convert the NSArray to an Applescript list
-- Note that since an NSSet is unordered, the converted list items may be in any order
set asValue to (its allObjects()) as list
else if (its isKindOfClass:((my ||'s NSDictionary)'s |class|())) as boolean then
-- If the input argument is a Cocoa NSDictionary or one of its subclasses, extract its keys as an NSArray and its values as an Applescript list
set dictionaryKeys to its allKeys()
set asValue to (its objectsForKeys:dictionaryKeys notFoundMarker:(null)) as list
else
-- Otherwise, get the Cocoa object's Applescript value by first making it the single item of a list, then extracting the list's item, which the Cocoa-Applescript bridge will convert to an Applescript value if the conversion is possible (if the conversion is not possible, the item will remain the original Cocoa object)
set asValue to (it as list)'s first item
end if
on error
-- Handle the case where the input argument is an Applescript value
if its class = record then
-- If the input argument is an Applescript record, extract its keys as an NSArray and its values as an Applescript list
tell ((my ||'s NSDictionary)'s dictionaryWithDictionary:it)
set dictionaryKeys to its allKeys()
set asValue to (its objectsForKeys:dictionaryKeys notFoundMarker:(null)) as list
end tell
else
-- Otherwise, get the input argument's Applescript value
set asValue to it
end if
end try
end tell
-- If the input argument was a Cocoa or Applescript collection, convert its extracted objects/values to Applescript values in a recursive fashion so that nested collections are converted properly
tell asValue
if its class = list then
-- Convert the extracted objects/values recursively
set asValue to {}
repeat with i in it
set end of asValue to my cocoaToASValue(i's contents)
end repeat
-- If the input argument was a Cocoa NSDictionary (or one of its subclasses) or an Applescript record, reconstruct it as an Applescript record from its extracted keys and converted values
if dictionaryKeys ≠ null then set asValue to ((my ||'s NSDictionary)'s dictionaryWithObjects:((my ||'s NSArray)'s arrayWithArray:asValue) forKeys:dictionaryKeys) as record
end if
end tell
-- Return the input argument's Applescript value
return asValue
end cocoaToASValue