Writing JSON data with NSJSONSerialization

json
foundation
how-to
asobjc

(Mark Alldritt) #1

Writing JSON data with NSJSONSerialization

As a bookend to my previous post on reading JSON data with NSJSONSerialization, here is a snippet of code demonstrating how to write an AppleScript record as JSON data. While my sample converts an AppleScript record to JSON, the same technique can be used with AppleScript lists as well.

Here is the AppleScript code:

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

-- classes, constants, and enums used
property NSJSONWritingPrettyPrinted : a reference to 1
property NSJSONSerialization : a reference to current application's NSJSONSerialization

--	An AppleScript data structure to convert to JSON
set theData to {|menu|:¬
	{|id|:"file", value:"File", popup:¬
		{menuitem:{¬
			{value:"New", onclick:"CreateNewDoc()"}, ¬
			{value:"Open", onclick:"OpenDoc()"}, ¬
			{value:"Close", onclick:"CloseDoc()"}}}}}

--	Ask for the file to write to
set theFile to choose file name

set theJSONData to NSJSONSerialization's dataWithJSONObject:theData options:NSJSONWritingPrettyPrinted |error|:(missing value)
theJSONData's writeToFile:(theFile's POSIX path) atomically:false

The result looks like this:

{
  "menu" : {
    "id" : "file",
    "value" : "File",
    "popup" : {
      "menuitem" : [
        {
          "value" : "New",
          "onclick" : "CreateNewDoc()"
        },
        {
          "value" : "Open",
          "onclick" : "OpenDoc()"
        },
        {
          "value" : "Close",
          "onclick" : "CloseDoc()"
        }
      ]
    }
  }
}

NOTE: the NSJSONWritingPrettyPrinted value passed to the NSJSONSerialization's dataWithJSONObject command causes the resulting JSON text to be pretty printed. If you want all the whitespace removed, pass 0:

set theJSONData to NSJSONSerialization's dataWithJSONObject:theData options:0 |error|:(missing value)

This results in the following output:

{"menu":{"id":"file","value":"File","popup":{"menuitem":[{"value":"New","onclick":"CreateNewDoc()"},{"value":"Open","onclick":"OpenDoc()"},{"value":"Close","onclick":"CloseDoc()"}]}}}

Write JSON Data.scpt.zip (6.0 KB)


#2

The corresponding entry in my snippet library is a slightly mis-named showJSON function – misnamed in the sense that it tries to return a JS-type stringification not only of records and lists, but also of the basic atomic data types:

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

-- 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, e} to ca's NSJSONSerialization's dataWithJSONObject:x options:1 |error|:(reference)
        if json is missing value then
            e's localizedDescription() as text
        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


(Shane Stanley) #3

You’re assuming the offset to GMT on the date is the same as it is when the script is run. I’m not sure that’s always a valid assumption.


(Jim Underwood) #4

How do we convert theJSONData to AppleScript text?

I tried:
set JSONStr to theJSONData as text

but it didn’t work.

OK, I think I have figured it out:

set ca to current application
set JSONStr to (ca's NSString's alloc()'s initWithData:theJSONData encoding:(ca's NSUTF8StringEncoding)) as text

Is this good, or is there better?


#5

Thanks – I’ll think about it.

I would like to strip things back to UTC + a Z, I guess I had thought that this provides a way of getting there - but perhaps it needs more reflection.

The behaviour of JSON.stringify() in JS itself is:

(() => {
    'use strict';

    const dte = new Date(Date.UTC(1985, 11, 2, 3, 5, 40))

    return {
        toString: dte.toString(),
        toJSON: dte.toJSON()
    };
    // {
    //  "toString":"Mon Dec 02 1985 03:05:40 GMT+0000 (GMT)",
    //  "toJSON":"\"1985-12-02T03:05:40.000Z\""
    // }
})();

(Shane Stanley) #6

It’s best :slight_smile:


(Shane Stanley) #7

Don’t think about it too long — AppleScript dates are bad for brain health.

Last time I looked it up there was no canonical format for JSON dates defined, but RFC3339 is widely used.


#8

( Impossible to get ones head around a spinning planet – interstellar travel and relativistic date-times are a software nightmare – complete non-starter )