What is AppleScript Equivalent to JavaScript Associative Arrays?

JavaScript associative arrays make it very, very easy to create and lookup data. For example:

var macOS = {"10.14":"Mojave", "10.15":"Catalina"};
var myOS = "10.14";
var myName = macOS[myOS];
myName;
//-->Mojave

Anyone know the best AppleScript equivalent for this? Is there a Script Library that would help?

NSMutableDictionary ?

Maybe Property Lists? Here’s an old discussion: https://macscripter.net/viewtopic.php?pid=64151#p64151

Some basic introspective and other ops in terms of NSMutableDictionary and NSDictionary

(Clearly more work than in JS, but not out of reach)

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

on run
    set macOS to dictFromList({{"10.14", "Mojave"}, {"10.15", "Catalina"}})
    
    --> {|10.14|:"Mojave", |10.15|:"Catalina"}
    
    
    keys(macOS)
    
    --> {"10.14", "10.15"}
    
    
    elems(macOS)
    
    --> {"Mojave", "Catalina"}
    
    
    assocs(macOS)
    
    --> {{"10.14", "Mojave"}, {"10.15", "Catalina"}}
    
    
    set macOSMore to insertDict("10.13", "High Sierra", insertDict("10.12", "Sierra", macOS))
    
    --> {|10.12|:"Sierra", |10.15|:"Catalina", |10.14|:"Mojave", |10.13|:"High Sierra"}
    
    
    set macOSLess to deleteKey("10.14", macOSMore)
    
    --> {|10.12|:"Sierra", |10.15|:"Catalina", |10.13|:"High Sierra"}
    
    
    script lookup
        on |λ|(k)
            maybe("Not found", my identity, lookUpDict(k, macOSLess))
        end |λ|
    end script
    map(lookup, {"10.11", "10.12", "10.13", "10.14", "10.15", "10.16", "10.17"})
    
    
    --> {"Not found", "Sierra", "High Sierra", "Not found", "Catalina", "Not found", "Not found"}
end run


-- REUSABLE GENERAL FUNCTIONS ---------------------------------------------------
-- https://github.com/RobTrew/prelude-applescript

-- Just :: a -> Maybe a
on Just(x)
    -- Constructor for an inhabited Maybe (option type) value.
    -- Wrapper containing the result of a computation.
    {type:"Maybe", Nothing:false, Just:x}
end Just

-- Nothing :: Maybe a
on Nothing()
    -- Constructor for an empty Maybe (option type) value.
    -- Empty wrapper returned where a computation is not possible.
    {type:"Maybe", Nothing:true}
end Nothing

-- assocs :: Map k a -> [(k, a)]
on assocs(m)
    set c to class of m
    if list is c then
        zip(enumFromTo(1, length of m), m)
    else if record is c then
        tell current application to set dict to ¬
            dictionaryWithDictionary_(m) of its NSDictionary
        zip((sortedArrayUsingSelector_("compare:") of ¬
            allKeys() of dict) as list, ¬
            (allValues() of dict) as list)
    else
        {}
    end if
end assocs

-- deleteKey :: k -> Dict -> Dict
on deleteKey(k, rec)
    tell current application to set nsDct to ¬
        dictionaryWithDictionary_(rec) of its NSMutableDictionary
    removeObjectForKey_(k) of nsDct
    nsDct as record
end deleteKey

-- elems :: Map k a -> [a]
-- elems :: Set a -> [a]
on elems(x)
    if record is class of x then -- Dict
        tell current application to allValues() ¬
            of dictionaryWithDictionary_(x) ¬
            of its NSDictionary as list
    else -- Set
        (allObjects() of x) as list
    end if
end elems

-- enumFromTo :: Int -> Int -> [Int]
on enumFromTo(m, n)
    if m ≤ n then
        set lst to {}
        repeat with i from m to n
            set end of lst to i
        end repeat
        lst
    else
        {}
    end if
end enumFromTo

-- dictFromList :: [(k, v)] -> Dict
on dictFromList(kvs)
    set tpl to unzip(kvs)
    script go
        on |λ|(x)
            x as string
        end |λ|
    end script
    tell current application
        (its (NSDictionary's dictionaryWithObjects:(item 2 of tpl) ¬
            forKeys:(my map(go, item 1 of tpl)))) as record
    end tell
end dictFromList

-- identity :: a -> a
on identity(x)
    -- The argument unchanged.
    x
end identity

-- insertDict :: String -> a -> Dict -> Dict
on insertDict(k, v, rec)
    tell current application
        tell dictionaryWithDictionary_(rec) of its NSMutableDictionary
            its setValue:v forKey:(k as string)
            it as record
        end tell
    end tell
end insertDict

-- keys :: Dict -> [String]
on keys(rec)
    tell current application
        (allKeys() of its (NSDictionary's dictionaryWithDictionary:rec)) as list
    end tell
end keys

-- lookupDict :: a -> Dict -> Maybe b
on lookUpDict(k, dct)
    -- Just the value of k in the dictionary,
    -- or Nothing if k is not found.
    set ca to current application
    set v to (ca's NSDictionary's dictionaryWithDictionary:dct)'s objectForKey:k
    if missing value ≠ v then
        Just(item 1 of ((ca's NSArray's arrayWithObject:v) as list))
    else
        Nothing()
    end if
end lookUpDict

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    -- The list obtained by applying f
    -- to each element of xs.
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map

-- maybe :: b -> (a -> b) -> Maybe a -> b
on maybe(v, f, mb)
    -- The 'maybe' function takes a default value, a function, and a 'Maybe'
    -- value.  If the 'Maybe' value is 'Nothing', the function returns the
    -- default value.  Otherwise, it applies the function to the value inside
    -- the 'Just' and returns the result.
    if Nothing of mb then
        v
    else
        tell mReturn(f) to |λ|(Just of mb)
    end if
end maybe

-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

-- min :: Ord a => a -> a -> a
on min(x, y)
    if y < x then
        y
    else
        x
    end if
end min

-- unzip :: [(a,b)] -> ([a],[b])
on unzip(xys)
    set xs to {}
    set ys to {}
    repeat with xy in xys
        set end of xs to item 1 of xy
        set end of ys to item 2 of xy
    end repeat
    return {xs, ys}
end unzip

-- zip :: [a] -> [b] -> [(a, b)]
on zip(xs, ys)
    set lng to min(length of xs, length of ys)
    set lst to {}
    repeat with i from 1 to lng
        set end of lst to {item i of xs, item i of ys}
    end repeat
    return lst
end zip

Thanks. Don’t actually use plists, but use Script Objects.
This seems to work quite well, although I have not tested it for large Records.
Note that by using the Vertical Bars it allows you to use numbers as the Record Key name, as in |10.14| (thanks to Chris @ccstone for this idea)

All comments and suggestions for improvement are welcome!


To Compare with JavaScript

set macOSRec to {|10.14|:"Mojave", |10.15|:"Catalina", |10.X|:"TBD"}

set myOS to "10.14"
set myOSnameHandler to my getRecordValue(macOSRec, myOS)
-->"Mojave"

set macOSRec to my setRecordValue(macOSRec, "10.X", "Name from Handler")

Full Script with Handlers

property ptyScriptName : "How To Use Records like JS Associative Arrays"
property ptyScriptVer : "1.0"
property ptyScriptDate : "2019-07-18"
property ptyScriptAuthor : "JMichaelTX"

(*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PURPOSE/METHOD:
  • Show how to Use Script Objects to Read/Write Key Values in Records

REQUIRED:
  1.  macOS 10.11.6+

REF:  The following were used in some way in the writing of this script.

  1.  2006-08-06, jobu, MacScripter
      Use a variable as a key to index into an associative array?
      https://macscripter.net/viewtopic.php?pid=64151#p64151

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)

set macOSRec to {|10.14|:"Mojave", |10.15|:"Catalina", |10.X|:"TBD"}

set myOS to "10.14"

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--»   Normal Method Using Key Literals
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

set myOSnameLit to |10.14| of macOSRec
set |10.X| of macOSRec to "New Name Using Lit"

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--»   New Method Using Variable For Keys in Script Objects
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

set myOSnameHandler to my getRecordValue(macOSRec, myOS)
set macOSRec to my setRecordValue(macOSRec, "10.X", "Name from Handler")

return macOSRec

--~~~~~~~~~~~~~~~~~~~~~~~ END OF MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on getRecordValue(pRec, pKeyStr)
  --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
  (*  VER: 2.0    2019-07-18
    PURPOSE:  GET the Value for given Record Key
    PARAMETERS:
      • pRec      | record |Record of Key/Value Pairs to use
      • pKeyStr    | text  |  Key name of Record to get

    RETURNS:  Value for the given Key

    AUTHOR:  JMichaelTX based on script by @jobu
    REF:      Use a variable as a key to index into an associative array?
                https://macscripter.net/viewtopic.php?pid=64151#p64151
  --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
  *)
  run script "on run{pRec, pKeyStr}
  return ( |" & pKeyStr & "|   of pRec )
  end" with parameters {pRec, pKeyStr}
end getRecordValue
--~~~~~~~~~~~~~~~~~~~~ END of Handler ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on setRecordValue(pRec, pKeyStr, pValue)
  --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
  (*  VER: 1.0    2019-07-18
      PURPOSE:  SET the Value for given Record Key
      PARAMETERS:
        • pRec      | Record  |Record
        • pKeyStr    | text    |  Key name of Record to set
        • pValue      |   any      |  New value (of any type) to be set
  
      RETURNS:  Full Record with new Value
  
      AUTHOR:  JMichaelTX based on script by @jobu
    --–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
    *)
  set pRec to run script "
    on run{pRec, pKeyStr, pValue}
      set |" & pKeyStr & "| of pRec to pValue
      return pRec
    end run" with parameters {pRec, pKeyStr, pValue}
  return pRec
end setRecordValue

One more basic op in terms of NSMutableDictionary – removing an entry.

-- deleteKey :: String -> Dict -> Dict
on deleteKey(k, rec)
	tell current application to set nsDct to ¬
		dictionaryWithDictionary_(rec) of its NSMutableDictionary
	removeObjectForKey_(k) of nsDct
	nsDct as record
end deleteKey

(added to longer example above)

(Using the its and _ rather than : and 's forms here simply because on some wiki software (the Tinderbox and Rosetta Code forums come to mind) the apostrophe scrambles syntax highlighting : -)

@ComplexPoint, I just now had time to look at your script. Thanks for sharing.

May I ask what is the purpose of this script object: script lookup?
You don’t appear to use it anywhere, or am I missing it?

Just below the definition:

map(lookup, ...