JSON export of a Music.app properties record

A snippet floating around the net uses the Foundation framework and NSJSONSerialization to serialize AppleScript records. However it chokes when the record was generated by Music.app.
Any idea of how to fix this? I have some experience with AS, but nothing with Cocoa.

I have also tried using “JSON Helper” from the App store, but it just returns an empty string.

use framework "Foundation"

-- pass a string, list, record or number, and either a path to save the result to, or missing value to have it returned as text
on convertASToJSON(someASThing)
	--convert to JSON data
	set {theData, theError} to current application's NSJSONSerialization's dataWithJSONObject:someASThing options:0 |error|:(reference)
	if theData is missing value then error (theError's localizedDescription() as text) number -10000
	-- convert data to a UTF8 string
	set someString to current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSUTF8StringEncoding)
	return someString as text
end convertASToJSON

set j to convertASToJSON({a:1, b:2})
log j -- works!
tell application "Music"
	set p to properties of track 1
	log my convertASToJSON(p) -- *** +[NSJSONSerialization dataWithJSONObject:options:error:]: Invalid top-level type in JSON write
end tell

I am not sure why do you want this, but let’s try.

Plain AppleScript solution may:

  1. create Custom Record according to Properties Record of music track,
  2. force coercing this record to text,
  3. retrieve JSON content from error message.

Custom Record’s (p’s here) keys should be placed into the pipes!
.

tell application "Music" to tell track 1
	set p to {|album|:album, |album artist|:album artist, |album disliked|:album disliked} & ¬
		{|album loved|:album loved, |artist|:artist, |bit rate|:bit rate} & ¬
		{|bookmark|:bookmark, |bookmarkable|:bookmarkable, |bpm|:bpm} & ¬
		{|category|:category, |cloud status|:(cloud status as text)} & ¬
		{|comment|:comment, |compilation|:compilation, |composer|:composer} & ¬
		{|database ID|:database ID, |date added|:date added, |description|:description}
	-- Every track has 57 properties. Add the rest properties in similar way, yorself.
	---- Remember to coerce Contant values to text, for example the value for Cloud Status has Constant value, so
	---- it should be coerced to text (see above)
end tell

try
	p as string
on error errTxt
	set {openBrace, closeBrace} to {offset of "{" in errTxt, offset of "}" in errTxt}
	set JSON to text openBrace thru closeBrace of errTxt
end try

set AppleScript's text item delimiters to "|"
set JSON to text items of JSON
set AppleScript's text item delimiters to ""
set JSON to JSON as text

AsObjC can do it as well, but it will wrap the keys by quotes. Note that the date added value should be coerced to string. Other way, it will throw error.
.

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

tell application "Music" to tell track 1
	set p to {|album|:album, |album artist|:album artist, |album disliked|:album disliked} & ¬
		{|album loved|:album loved, |artist|:artist, |bit rate|:bit rate} & ¬
		{|bookmark|:bookmark, |bookmarkable|:bookmarkable, |bpm|:bpm} & ¬
		{|category|:category, |cloud status|:(cloud status as text)} & ¬
		{|comment|:comment, |compilation|:compilation, |composer|:composer} & ¬
		{|database ID|:database ID, |date added|:(date added as text), |description|:description}
	-- Every track has 57 properties. Add the rest properties in similar way, yorself.
	---- Remember to coerce Contant values to text, for example the value for Cloud Status has Constant value, so
	---- it should be coerced to text (see above)
end tell

convertASToJSON(p)

-- pass a string, list, record or number, and either a path to save the result to, or missing value to have it returned as text
on convertASToJSON(someASThing)
	--convert to JSON data
	set {theData, theError} to current application's NSJSONSerialization's dataWithJSONObject:someASThing options:0 |error|:(reference)
	if theData is missing value then error (theError's localizedDescription() as text) number -10000
	-- convert data to a UTF8 string
	set someString to current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSUTF8StringEncoding)
	return someString as text
end convertASToJSON

The quotes are needed in JSON. And yes, JSON does not support objects like Date (but Arrays).

There’s a cottage industry of programs that work on Music.app metadata. Some of them do a less than stellar job (BeaTunes), so I need to be able to export my metadata as a backup/reference point to later reimport it as needed. So I was hoping that dumping to / from JSON would make things easier.

Thank you for your solutions. Actually my current program calls AS routines from a Python script, where I do a similar translation between AppleEvents and strings… I hoped that there would be a less manual / brute-force-ish strategy than this. Oh well.

Here is full plain AppleScript version.

It counts all of the 57 properties & puts the key names into quotes, providing JSON similar to AsObjC result.

tell application "Music" to tell track 1 to set p to ¬
	{|"album"|:album, |"album artist"|:album artist, |"album disliked"|:album disliked} & ¬
	{|"album loved"|:album loved, |"artist"|:artist, |"bit rate"|:bit rate} & ¬
	{|"bookmark"|:bookmark, |"bookmarkable"|:bookmarkable, |"bpm"|:bpm} & ¬
	{|"category"|:category, |"cloud status"|:(cloud status as text)} & ¬
	{|"comment"|:comment, |"compilation"|:compilation, |"composer"|:composer} & ¬
	{|"database ID"|:database ID, |"date added"|:date added as text, |"description"|:description} & ¬
	{|"disk count"|:disc count, |"disk number"|:disc number, |"disliked"|:disliked} & ¬
	{|"duration"|:duration, |"enabled"|:enabled, |"episode number"|:episode number} & ¬
	{|"EQ"|:EQ, |"finish"|:finish, |"genre"|:genre, |"grouping"|:grouping, |"id"|:id} & ¬
	{|"index"|:index, |"kind"|:kind, |"loved"|:loved, |"media kind"|:media kind} & ¬
	{|"modification date"|:modification date as text, |"movement"|:movement} & ¬
	{|"movement count"|:movement count, |"movement number"|:movement number} & ¬
	{|"name"|:name, |"persistent ID"|:persistent ID, |"played count"|:played count} & ¬
	{|"rating"|:rating, |"sample rate"|:sample rate, |"shufflable"|:shufflable, |"size"|:size} & ¬
	{|"skipped count"|:skipped count, |"sort album"|:sort album, |"sort artist"|:sort artist} & ¬
	{|"sort composer"|:sort composer, |"sort name"|:sort name, |"start"|:start, |"time"|:time} & ¬
	{|"track count"|:track count, |"track number"|:track number, |"unplayed"|:unplayed} & ¬
	{|"volume adjustment"|:volume adjustment, |"work"|:work, |"year"|:year}

try
	p as string
on error errTxt
	set {openBrace, closeBrace} to {offset of "{" in errTxt, offset of "}" in errTxt}
	set JSON to text openBrace thru closeBrace of errTxt
end try

set AppleScript's text item delimiters to "|"
set JSON to text items of JSON
set AppleScript's text item delimiters to ""
set JSON to JSON as text