1

In reference to this question,

How to download and get values from JSON file using VBScript or batch file?

how to get the values from JSON file that looks like this,

["AA-BB-CC-MAKE-SAME.json","SS-ED-SIXSIX-TENSE.json","FF-EE-EE-EE-WW.json","ZS-WE-AS-FOUR-MINE.json","DD-RF-LATERS-LATER.json","FG-ER-DC-ED-FG.json"]

using AppleScript in MAC OS?

Here is part of VBScript code in Windows provided by Hackoo,

strJson = http.responseText
Result = Extract(strJson,"(\x22(.*)\x22)")
Arr = Split(Result,",")
For each Item in Arr
    wscript.echo Item
Next
'******************************************
Function Extract(Data,Pattern)
   Dim oRE,oMatches,Match,Line
   set oRE = New RegExp
   oRE.IgnoreCase = True
   oRE.Global = True
   oRE.Pattern = Pattern
   set oMatches = oRE.Execute(Data)
   If not isEmpty(oMatches) then
       For Each Match in oMatches  
           Line = Line & Trim(Match.Value) & vbCrlf
       Next
       Extract = Line
   End if
End Function
'******************************************

In MAC OS AppleScript I only need the code to get the values of the JSON file to a single array of string values. The above shown example above the VBScript is the how JSON file contents looks like.

Codename K
  • 890
  • 4
  • 23
  • 52
  • 3
    Have a look at: [JSON Helper for AppleScript](https://itunes.apple.com/us/app/json-helper-for-applescript/id453114608?mt=12) – user3439894 Jul 31 '18 at 16:52

2 Answers2

2

Short answer: Unfortunately, AppleScript doesn't provide a built-in feature to parse JSON which is analogous to JavaScript's JSON.parse() method.

Below are a couple of solutions:

  • Solution 1: Requires a third party plug-in to be installed, which may not always be feasible.
  • Solution 2: Does not require any third party plug-in to be installed, and instead utilizes tools/features built-in to macOS as standard.

Solution 1:

If you have the luxury of being able to install a third-party plugin on your users systems then you can install JSON Helper for AppleScript (As suggested by @user3439894 in the comments).

Then use it in your AppleScript as follows:

set srcJson to read POSIX file (POSIX path of (path to home folder) & "Desktop/foobar.json")
tell application "JSON Helper" to set myList to read JSON from srcJson

Explanation:

  1. On line 1 we read the contents of the .json file and assign it to the variable named srcJson.

    Note You'll need to change the path part (i.e. Desktop/foobar.json) as necessary.

  2. On line 2 we parse the contents using the JSON Helper plug-in. This assigns each item of the source JSON Array to a new AppleScript list. The resultant AppleScript list is assigned to a variable named myList.


Solution 2:

By utilizing tools built-in to macOS as standard, you can also do the following via AppleScript. This assumes that your JSON file is valid and contains a single Array only:

set TID to AppleScript's text item delimiters
set AppleScript's text item delimiters to ","
set myList to text items of (do shell script "tr ''\\\\n\\\\r''  ' ' <~/Desktop/foobar.json | sed 's/^ *\\[ *\"//; s/ *\" *\\] *$//; s/\" *, *\"/,/g;'")
set AppleScript's text item delimiters to TID

Note: you'll need to change the path part (i.e. ~/Desktop/foobar.json) as necessary.

Also, if your .json filename includes a space(s) you'll need to escape them with \\. For instance ~/Desktop/foo\\ bar.json

Explanation:

  1. On line 1 AppleScript's current text item delimiters are assigned to a variable named TID.

  2. On line 2 AppleScript's text item delimiters are set to a comma - this will help when extracting each individual value from the source JSON Array and assigning each value to a new AppleScript list.

  3. On line 3 a shell script is executed via the do shell script command, which performs the following:

    • Reads the content of the source .json file via the part which reads ~/Desktop/foobar.json. This path currently assumes the file is named foobar.json and resides in your Desktop folder (You'll need to change this path to wherever your actual file exists).

    • The content of foobar.json is redirected, (note the < before the filepath), to tr (i.e. the part which reads: tr ''\\\\n\\\\r'' ' '). This translation will replace any newline characters which may exists in the contents of the source .json Array with space characters. This ensures the contents of foobar.json is transformed to one line.

      Note: A JSON Array can contain newlines between each item and still be valid, so although the example JSON given in your question appears on one line - it is not a requirement of this solution as it will handle multi-line too.

    • The one line of text is then piped to sed's s command for further processing (i.e. the part which reads: | sed 's/^ *\\[ *\"//; s/ *\" *\\] *$//; s/\" *, *\"/,/g;').

      The syntax of the s command is 's/regexp/replacement/flags'.

      Let's breakdown each s command to further understand what is happening:

      • s/^ *\\[ *\"// removes the opening square bracket [, which may be preceded or followed by zero or more space characters, and the following double quote (i.e. the first occurrence) from the beginning of the string.

      • s/ *\" *\\] *$// removes the closing square bracket ], which may be preceded or followed by zero or more space characters, and the preceding double quote (i.e. the last occurrence) from the end of the string.

      • s/\" *, *\"/,/g replaces single commas, (which may be preceded with zero or more spaces, and/or followed by zero or more spaces) with a single comma.

    • The initial part on line 3 which reads; set myList to text items of ... utilizes text items to read the String into an AppleScript list using commas as delimiters to determine each item of the list. The resultant Array is assigned to a variable named myList.

  4. On line 4 AppleScript's text item delimiters are restored to their original value.

Utilizing a variable for the source JSON filepath.

If you want to utilize a variable for the filepath to the source .json file then you can do something like this instead:

set srcFilePath to quoted form of (POSIX path of (path to home folder) & "Desktop/foobar.json")

set TID to AppleScript's text item delimiters
set AppleScript's text item delimiters to ","
set myList to text items of (do shell script "tr ''\\\\n\\\\r'' ' ' <" & srcFilePath & " | sed 's/^ *\\[ *\"//; s/ *\" *\\] *$//; s/\" *, *\"/,/g;'")
set AppleScript's text item delimiters to TID

Note This is very much the same as the first example. The notable differences are:

  • On the first line we assign the filepath to a variable named srcFilePath.
  • In the do shell script we reference the srcFilePath variable.

Additional note regarding JSON escaped special characters: Solution 2 preserves any JSON escaped special characters which may be present in the values of source JSON array. However, Solution 1 will interpret them.

Caveats Solution 2 produces unexpected results when an item in the source JSON array includes a comma because a comma is used as a text item delimiters.

RobC
  • 22,977
  • 20
  • 73
  • 80
  • 2
    I know in your example the filename is `foobar.json` however, if you're going to use a _variable_, e.g. `srcFilePath `, that holds a `POSIX path` for use with a `do shell script` _command_, then I'd place it within _single-quotes_ regardless. Say the filename is `foo bar.json`, then as presently coded it fails because of an unescaped space or not being quoted. Use the following instead: `set srcFilePath to quoted form of (POSIX path of (path to home folder) & "Desktop/foo bar.json")` Then it works without or without spaces in the path filename. – user3439894 Aug 01 '18 at 14:37
  • 1
    Good thinking with regards to the _newline-space_ substitutions. – CJK Aug 01 '18 at 16:13
  • 1
    With standard AppleScript, the use of the word "array", regardless of case, in "... to a new AppleScript Array. The resultant AppleScript array ..." is somewhat misleading since standard AppleScript does not have an `array` _object_ however, it does have a `list` _object_. – user3439894 Aug 01 '18 at 17:35
1

How to get the values from JSON file that looks like this,

["AA-BB-CC-MAKE-SAME.json","SS-ED-SIXSIX-TENSE.json","FF-EE-EE-EE-WW.json","ZS-WE-AS-FOUR-MINE.json","DD-RF-LATERS-LATER.json","FG-ER-DC-ED-FG.json"]

If you actually mean what you wrote, and that the contents of the JSON file is that list of six strings in a single array, formatted on a single line, the simplest way is to treat it as text, trim the opening and closing square brackets, then delimit its fields at every occurrence of a ,. Finally, each individual text item can have the surrounding quotes trimmed as well.

Examining the VBScript, it looks like it uses a very similar process, albeit with regular expressions, which AppleScript doesn't feature but which aren't especially necessary in this simple situation.

Let's assume that the JSON array above is stored in a file on your desktop called "myfile.json". Then:

    set home to the path to home folder
    set f to the POSIX path of home & "Desktop/myfile.json"
    
    set JSONstr to read POSIX file f
    
    # Trim square brackets
    set JSONstr to text 2 thru -2 of JSONstr
    
    # Delimit text fields using comma
    set the text item delimiters to ","
    set Arr to the text items of JSONstr
    
    # Trim quotes of each item in Arr
    repeat with a in Arr
        set contents of a to text 2 thru -2 of a
    end repeat
    
    # The final array
    Arr

I only need the code to get the values of the JSON file to a single array of string values. The above shown example above the VBScript is the how JSON file contents looks like.

The variable Arr now contains the array (referred to as lists in AppleScript) of string values. You can access a particular item in it like this:

    item 2 of Arr --> "SS-ED-SIXSIX-TENSE.json"

A More General Solution

I've decided to include a more advanced way to handle JSON in an AppleScript, partly because I've been doing a lot of JSON processing quite recently and this is all fresh on my event horizon; but also to demonstrate that, using AppleScriptObjC, parsing even very complex JSON data is not only possible, but quite simple.

I don't think you'll need it in this specific case, but it could come in useful for some future situation.

The script has three sections: it starts off importing the relevant Objective-C framework that gives AppleScript additional powers; then, I define the actual handler itself, called JSONtoRecord, which I describe below. Lastly, comes the bottom of the script where you can enter your code and do whatever you like with it:

    use framework "Foundation"
    use scripting additions
    --------------------------------------------------------------------------------
    property ca : a reference to current application
    property NSData : a reference to ca's NSData
    property NSDictionary : a reference to ca's NSDictionary
    property NSJSONSerialization : a reference to ca's NSJSONSerialization
    property NSString : a reference to ca's NSString
    property NSUTF8StringEncoding : a reference to 4
    --------------------------------------------------------------------------------
    on JSONtoRecord from fp
        local fp
        
        set JSONdata to NSData's dataWithContentsOfFile:fp
        
        set [x, E] to (NSJSONSerialization's ¬
            JSONObjectWithData:JSONdata ¬
                options:0 ¬
                |error|:(reference))
        
        if E ≠ missing value then error E
        
        tell x to if its isKindOfClass:NSDictionary then ¬
            return it as record
        
        x as list
    end JSONtoRecord
    --------------------------------------------------------------------------------
    ###YOUR CODE BELOW HERE
    #
    #
    set home to the path to home folder
    set f to the POSIX path of home & "Desktop/myfile.json"
    
    JSONtoRecord from f
    
    --> {"AA-BB-CC-MAKE-SAME.json", "SS-ED-SIXSIX-TENSE.json", ¬
    --> "FF-EE-EE-EE-WW.json", "ZS-WE-AS-FOUR-MINE.json", ¬
    --> "DD-RF-LATERS-LATER.json", "FG-ER-DC-ED-FG.json"}

At the bottom of the script, I've called the JSONtoRecord handler, passing it the location of myfile.json. One of the benefits of this handler is that it doesn't matter whether the file is formatted all on one line, or over many lines. It can also handle complex, nested JSON arrays.

In those instances, what it returns is a native AppleScript record object, with all the JSON variables stored as property values in the record. Accessing the variables then becomes very simple.

This is actually exactly what the JSON Helper application that a couple of people have already mentioned does under the hood.

The one criterion (other than the JSON file containing valid JSON data) is that the path to the file is a posix path written in full, e.g. /Users/CK/Desktop/myfile.json, and not ~/Desktop/myfile.json or, even worse, Macintosh HD:Users:CK:Desktop:myfile.json.

Community
  • 1
  • 1
CJK
  • 5,732
  • 1
  • 8
  • 26
  • Why can't I replace `dataWithContentsOfFile` with `dataWithContentsOfURL`? What do I have to do to make that work (so I don't have to write a temporary file)? – hepcat72 Mar 16 '21 at 13:39
  • @hepcat72 You can use `dataWithContentsOfURL:`, or `dataWithContentsOfURL:options:error:`. However, a file URL and a file path are different data types, so I suspect you're passing a string file path to the `JSONtoRecord` handler, where you need to pass a `POSIX file`, `alias`, or `«class furl»` object. I'll make an edit to the handler that will allow it to cope with any of these possibilities so you won't have to worry. I'll do it when I get home. – CJK Mar 19 '21 at 16:48
  • I actually am trying to retrieve JSON from a (local network) web URL (the channel lineup from my HDHomeRun) so I can process it and have the user select a channel. So my comment was imprecise. Not only do I want to replace the method - I want to pass it a web URL starting with https://. I tried "as url" and also tried unsuccessfully declaring an nsurl. – hepcat72 Mar 19 '21 at 19:57
  • Can dataWithContentsOfURL work on a web address? Or is it just for files? – hepcat72 Mar 21 '21 at 13:53
  • 1
    @hepcat72 Apologies, I hadn't forgotten about this, but my laptop broke, internet went down for days, and my day job just keeps fucking me up the arse. `dataWithContentsOfURL:` can, indeed, be used for remote URLs of almost any nature, provided the amount of data to be transferred over a network is relatively small (there are more appropriate, and faster methods for large data transfers). You have to construct the NSURL by way of `NSURL's URLWithString:"https://example.com:448/foo?a=3&b=..."`. – CJK Mar 26 '21 at 13:31