Textual representation of AS list

So I have this code:

set aList to {"one", "two", "three", "four"}
set aString to aList as text

which results in this output:

onetwothreefour

But what I want is this:

set aList to {"one", "two", "three", "four"}
-- do something here, I dunno what

which will result in this output:

{"one", "two", "three", "four"}

So, like, the literal representation of the AppleScript list code in the script.

I am open to a native AS solution, or something using ASObjC, or even a Unix command I can run via do shell script.

Here’s the solution I have at the moment and it works but I kinda think it sucks.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property jsScript : ".split('|||').map(w => `\"${w}\"`).join(',').replace(/^/, '{').replace(/$/, '}')"

set aList to {"one", "two", "three", "four"}
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to {"|||"}
set aString to aList as string
set AppleScript's text item delimiters to saveTID

set scpt to quoted form of ((quoted form of aString) & jsScript)
set output to do shell script "osascript -l JavaScript -e " & scpt
log output

Which produces in SD’s event log:

(*{"one","two","three","four"}*)

Any suggestions?

Bonus round question!

Take this input:

{{"A","B","C"}, {"D","E","F"}}

and turn it into:

{"A","B","C";"D","E","F"}

There’s no good way to do what you want with complex inner data types. All approaches have their broken edge cases.

If it’s just strings, you do:

set AppleScript's text item delimiters to "\", \""
set aList_as_string to "{\"" & (aList as string) & "\"}"
set AppleScript's text item delimiters to ""

Other approaches use osascript with the -s s option from the command line, compiling/decompiling a temporary script on-disk (osadecompile), or intentionally throwning an error with the list variable & parsing the error message. ASOC can work as well, but will just give wrapper types for any unbridged AppleScript types.

As @tree_frog mentioned, ASOC can help; my approach would be something like this:

(current application's NSArray's arrayWithArray:myList)'s debugDescription()

(note: untested code)

Below is code I wrote for this use. It seems to convert any sort of AS-related data to a string. At least, haven’t found an exception yet

Stan C.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
set aList to {"one", "two", "three", "four"}
convertAnythingToString(aList)
--> "{\"one\", \"two\", \"three\", \"four\"}"

on convertAnythingToString(something)
     if class of something is in {text, Unicode text} then
            set theString to ("\"" & something & "\"")
	else if something is {} then
		set theString to "{}"
	else if something is in {missing value, null, true, false} then
		set theString to something as text
	else
		try
			something as integer -- force an error, if not an integer
			set theString to something as text
		on error errText
			set opening to offset of "{" in errText
			set closing to offset of "}" in "" & (reverse of (characters of errText))
			set theString to text opening through -closing of errText
		end try
	end if
	return theString
end convertAnythingToString

As @tree_frog points out, there’s no single clean solution here – it really depends on what you want to use the generated stringifications for.

Not sure if any of the sketches below, (which originated some time ago in a use case which may be rather different from yours) contain anything which you can use:

use framework "Foundation"
use scripting additions

on run
    set xs to {7.5, "Beta", current date}
    
    showList(xs)
end run

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

-- intercalateS :: String -> [String] -> String
on intercalateS(delim, xs)
    set {dlm, my text item delimiters} to ¬
        {my text item delimiters, delim}
    set s to xs as text
    set my text item delimiters to dlm
    s
end intercalateS

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


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


-- show :: a -> String
on show(e)
    set c to class of e
    if c = list then
        showList(e)
    else if c = record then
        set mb to lookupDict("type", e)
        if Nothing of mb then
            showDict(e)
        else
            script
                on |λ|(t)
                    if "Either" = t then
                        set f to my showLR
                    else if "Maybe" = t then
                        set f to my showMaybe
                    else if "Ordering" = t then
                        set f to my showOrdering
                    else if "Ratio" = t then
                        set f to my showRatio
                    else if class of t is text and t begins with "Tuple" then
                        set f to my showTuple
                    else
                        set f to my showDict
                    end if
                    tell mReturn(f) to |λ|(e)
                end |λ|
            end script
            tell result to |λ|(Just of mb)
        end if
    else if c = date then
        "\"" & showDate(e) & "\""
    else if c = text then
        "\"" & e & "\""
    else if (c = integer or c = real) then
        e as text
    else if c = class then
        "null"
    else
        try
            e as text
        on error
            ("«" & c as text) & "»"
        end try
    end if
end show


-- ISO 8601 UTC 
-- showDate :: Date -> String
on showDate(dte)
    ((dte - (time to GMT)) as «class isot» as string) & ".000Z"
end showDate

-- showDict :: Dict -> String
on showDict(dct)
    showJSON(dct)
end showDict


-- showJSON :: a -> String
on showJSON(x)
    set c to class of x
    if (c is list) or (c is record) then
        set ca to current application
        set json to ca's NSJSONSerialization's dataWithJSONObject:x options:1 |error|:(missing value)
        if json is missing value then
            "Could not serialize as JSON"
        else
            (ca's NSString's alloc()'s initWithData:json encoding:(ca's NSUTF8StringEncoding)) as text
        end if
    else if c is date then
        "\"" & ((x - (time to GMT)) as «class isot» as string) & ".000Z" & "\""
    else if c is text then
        "\"" & x & "\""
    else if (c is integer or c is real) then
        x as text
    else if c is class then
        "null"
    else
        try
            x as text
        on error
            ("«" & c as text) & "»"
        end try
    end if
end showJSON


-- showRatio :: Ratio -> String
on showRatio(r)
    set s to (n of r as string)
    set d to d of r
    if 1 ≠ d then
        s & "/" & (d as string)
    else
        s
    end if
end showRatio


-- showList :: [a] -> String
on showList(xs)
    "{" & intercalateS(", ", map(my show, xs)) & "}"
end showList

What about a cruder approach?

set TheList to {{"one", "two", "three", "four"}, {"five", "six", "seven", "eight"}}

try
	set y to TheList + 1
on error t
end try

set AppleScript's text item delimiters to {"Can’t make ", " into type number"}
set a to (text item 2 of t)
log a

The following takes a similar approach to that suggested by tree_frog except it uses ASObjC. There’s probably no reason to use this unless the script otherwise uses ASObjC.

use framework "Foundation"
use scripting additions

set theList to {"one", "two", "three", "four"}
set theArray to current application's NSArray's arrayWithArray:theList
set theString to theArray's componentsJoinedByString:("\", \"")
set theString to current application's NSString's stringWithFormat_("{\"%@\"}", theString) as text
--> "{\"one\", \"two\", \"three\", \"four\"}"

An ASObjC suggestion for the OP’s second question.

use framework "Foundation"
use scripting additions

set theList to {{"A", "B", "C"}, {"D", "E", "F"}}
set theArray to current application's NSArray's arrayWithArray:theList
set stringOne to (theArray's objectAtIndex:0)'s componentsJoinedByString:("\", \"")
set stringTwo to (theArray's objectAtIndex:1)'s componentsJoinedByString:("\", \"")
set theString to current application's NSString's stringWithFormat_("{\"%@\"; \"%@\"}", stringOne, stringTwo) as text
--> "{\"A\", \"B\", \"C\"; \"D\", \"E\", \"F\"}"

This wasn’t the OP’s question but just for future reference, the following will flatten and create a string of a list of lists:

use framework "Foundation"
use scripting additions

set theArray to current application's NSArray's arrayWithArray:{{"A", "B", "C"}, {"D", "E", "F"}}
set theArray to (theArray's valueForKeyPath:"@unionOfArrays.self") -- flatten array of arrays
set theString to theArray's componentsJoinedByString:("\", \"")
set theString to current application's NSString's stringWithFormat_("{\"%@\"}", theString) as text
--> "{\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"}"
1 Like

My goal here is to be able to use AS lists as arrays in Excel formulas.

So, for instance (to use a very simple example), in Excel one can do:

=UPPER({"one","two","three","four"})

But in AS one can’t just do something like this:

set aaa to {"one","two","three","four"}
tell application "Microsoft Excel" to evaluate name "=UPPER(" & aaa ")"

Because Excel doesn’t know what to do with aaa. It would instead need to be transformed into a string {"one","two","three","four"} so it could be included in the formula passed to evaluate name:

tell application "Microsoft Excel" to ¬
	evaluate name "=UPPER({\"one\",\"two\",\"three\",\"four\"})"

So my goal was to somehow transform the AS list into a string:

set aaa to {"one","two","three","four"}
--do something to aaa here
tell application "Microsoft Excel" to evaluate name "=UPPER(" & aaa ")"
--which would then be the equivalent of manually writing it out as:
--tell application "Microsoft Excel" to ¬
--	evaluate name "=UPPER({\"one\",\"two\",\"three\",\"four\"})"

If that makes sense.

Of course, I would want to be able to do this not just with strings but also with numbers as well.

Thanks everyone for all your suggestions! I have a number of things to try now.

Oh, and to explain this part for those who aren’t familiar with Excel’s notation:

{"A","B","C";"D","E","F"} is a two-dimensional array in Excel, with columns separated by , and rows by ;.

You may find some relevant functions (e.g. excelArrayConstantFromTSV though it’s JXA rather than AS) in these two Keyboard Maestro macros:

ExcelCopyAsArrayConstants.kmmacros.zip (3.9 KB)

set theList to {{"A", "B", "C"}, {"D", "E", "F"}}
set AppleScript's text item delimiters to {"\",\""}
set item 1 of theList to "\"" & (item 1 of theList as text) & "\""
set item 2 of theList to "\"" & (item 2 of theList as text) & "\""
set AppleScript's text item delimiters to {";"}
return "{" & (theList as text) & "}"
--{"A","B","C";"D","E","F"}

And the original, simpler question:

set aList to {"one", "two", "three", "four"}
set AppleScript's text item delimiters to {"\", \""}
return "{\"" & (aList as text) & "\"}"
{"one", "two", "three", "four"}

I was curious how this might be done if the number of sublists in the list (each of which is apparently a spreadsheet row) was not a fixed number. Ed’s suggestion is easily modified to accomplish this, but the ASObjC solution requires a bit more work. The timing results were less than a tenth of a millisecond and half a millisecond, respectively.

AppleScript Solution

set theList to {{"A", "B", "C"}, {"D", "E", "F"}, {"G", "H", "I"}}
set {TID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {"\",\""}}
repeat with i from 1 to (count theList)
	set item i of theList to "\"" & (item i of theList as text) & "\""
end repeat
set AppleScript's text item delimiters to {";"}
set excelList to "{" & (theList as text) & "}"
set AppleScript's text item delimiters to TID
return excelList --> "{\"A\",\"B\",\"C\";\"D\",\"E\",\"F\";\"G\",\"H\",\"I\"}"

ASObjC Solution

use framework "Foundation"
use scripting additions

set theList to {{"A", "B", "C"}, {"D", "E", "F"}, {"G", "H", "I"}}
set excelList to getExcelList(theList) --> "{\"A\",\"B\",\"C\";\"D\",\"E\",\"F\";\"G\",\"H\",\"I\"}"

on getExcelList(theList)
	set theArray to current application's NSArray's arrayWithArray:theList
	set newArray to current application's NSMutableArray's new()
	repeat with anArray in theArray
		set aString to (anArray's componentsJoinedByString:("\",\""))
		(newArray's addObject:aString)
	end repeat
	set excelList to (newArray's componentsJoinedByString:("\";\""))
	return (current application's NSString's stringWithFormat_("{\"%@\"}", excelList)) as text
end getExcelList
1 Like