Convert AppleScript response to JSON - objective-c

I have a Cocoa application, part of it takes AppleScript from the user in a web view. I currently can pass a single string from a command (e.g. the current iTunes song name), however, if I run a command that returns a record (I believe that's what it is based on my research, could be wrong) such as the below I get '(null)' as the stringValue.
tell application "iTunes"
get properties of current track
end tell
If I run this in Script Debugger I can get this as a table shown below, so it's clearly possible.
However, nothing I've tried seems to be working. Based on a number of SO answers I've tried different ways, such as looping over every descriptor index as in this question. However, this doesn't work anymore, as it seems the key is not included in the array.
So, basically, I need to be able to convert an AppleScript output to JSON. I have a serialiser so that's not the issue, as long as I can get them in to Cocoa objects I'm set. What's the best way to do this?
Thanks

Many months late as an answer, but in case you or anyone else cares, here is what I did with a similar problem.
Check out the (sadly defunct) AppScript framework. That combined with lightly modified SBJSON let me convert any AppleScript record into JSON via Cocoa objects.
I used it in JSON Helper which is free on the Mac AppStore. You can also see the source to an earlier version on Google code here, which might be useful if you want to use the modified version of SBJSON.
In my example below the AppleScript record is being supplied via a scripting command.
#implementation makeJSONFromRecord
- (id)performDefaultImplementation {
NSDictionary *asRecord;
NSString *result;
AEMCodecs *codecs = [[AEMCodecs alloc] init];
// Use appscript framework to unpack the event into an object we can use
asRecord =[codecs unpack:[self directParameter]];
[codecs release];
// Use the JSON framework to convert the object to JSON notation
result = [asRecord JSONRepresentation];
if (result==nil) {
//We failed to create any valid JSON so return nothing
NSLog(#"Failed to make JSON from: %#", asRecord);
result=#"";
}
// Return the result to the applescript
return result;
}
#end

Related

Prevent NSDocument saving in temporary dictionary

I have an app with subclass of NSDocument that has overridden method writeToURL:(NSURL *) ofType:(NSString *) error:(NSError **) which saves data at given NSURL location, but also can save additional file (with appended .my2ext) with debug information. Previously it worked well (I created the app several years ago), but now I see that instead of user selected location the method gets some temporary directory:
file:///var/folders/yv/gwf3_hjs0ps7sb3psh3d0w3m0000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20MyApp%202)/myfilename.myext
Then, as I understand, the framework relocates the main file (at given url), but the additional file gets lost. So, can I somehow obtain the user selected path to save directly into it? Or prevent using temp directories at all?
I've already turned off the SandBox mode, but this didn't help. I also know that I can use "File Package" approach, but my app is created for a few people only, so, there is not interest in good production approach, only in simplicity.
I tried to google any possible solution, but found nothing helpful or just related. Even the documentation says nothing about using temporary directories! So, I decided to override different NSDocument methods. After several experiments I almost lost hope, but then I found that the method
saveToURL: ofType: forSaveOperation: delegate: didSaveSelector: contextInfo: provides real, user selected location. And this finally solved the problem.

Use RESTKit for two way synchronization

is it possible to use RESTKit for two way synchronization?
I played aroud with RESTKit and CoreDate. Now I can download all data from my REST service and all changes (create/modify/delete objects) in CoreDate will be overwritten by RESTKit.
Now I want to choose between both versions (the local version or the remote version). How do I do this? Is there a way to manipulat the mapping, or something like that?
update:
My app should synchronize after some changes or after a specific delay (not difficult). Every object has a change date. Until now I want to keep the newer one (if they are equal the local one).
I hope RestKit is made for changing the strategy how it merges objects. something like a block I can set, where I get both objects and can return the merged object.
What I got so far:
I load the object via RestKit but do not persist them. Also I setup a CoreData store where I store the local objects. After loading the remote object I start to synchronize my self. First searching for pairs and then decide which to take, delete, create, overwrite, and so on...
But this is a big bunch of work and I think RestKit is doing something similar. Why not simply changing the strategy of RestKit for my requirements.
Well this would be the "syncing down" thing. After this I could write the synchronized data set back to the service. The changes are not very frequently so I will not have to check for new changes.
I hope now it's a little bit clearer
What you really want to do is validate the data coming in.
Since RestKit is using CoreData it automatically uses the validation built into CoreData (see here)
Here is an example that will ensure that the date never gets changed to an earlier one.
- (BOOL) validateChangeDate:(id *)ioValue error: (NSError **)outError {
if ([[*ioValue laterDate:self.changeDate] compare:self.changeDate] == NSOrderedSame)
*ioValue = self.changeDate;
return YES;
}
Note: There may be better/faster ways to test to see if we need to change the date
Note 2: According to the docs, if RestKit sees a value rejected, it fails the entire object, which we don't want, so that's why we always return YES and just change the incoming date
Note 3: We also don't want to change the value unless we have to because of what the CoreData docs say here
You may be able to leverage KVC validation to achieve this as it allows you to check and then edit or deny the change for each object and key. Check these docs.

Storing objects in iOS for later use

My app is pulling in JSON data from our web service. In all instances up til now, the information would be retained in memory or just refreshed on the fly with no need to retain anything locally other than the login token used in the API. With one of the new features we are adding, we will be taking in a group of locations, 26 max, with long, lat, radius, and name.
I also need to add 1-2 fields to this data to create a larger object. So my question is, what would be the best way to store this type of data in the iOS filesystem? Currently I have been using the NSUserDefaults, but that seems sort of limited or ill advised for larger amounts of data. Maybe not.
This data will need to be retrieved, changed or edited, and resaved. All of this while still retaining the ability to pull any of those 26 objects. Thank you in advance for reading and helping out.
For such a small amount of data (26 items) I suggest archiving.
Save to plist using NSKeyedArchiver/NSKeyedUnarchiver. Read your data from the delegate's didFinishLaunchingWithOptions, and listen for UIApplicationWillResignActiveNotification to save it.
A NSUserDefaults is a plist with features designed to store user preferences. It's often used instead a regular plist to save a couple of lines of code, which I think it's a bad idea because you get an additional complexity unrelated to your task.
If you want the login to be protected against someone stealing the device and performing forensics use the Keychain. You may want to use a wrapper and read some articles, comment if you are interested.
If you look for more features see Best way to store data on iphone but doesn't seem to be the case now.
Some code to get you started... Register to call save on app resign:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(saveMyData)
name:UIApplicationWillResignActiveNotification
object:nil];
On each object of the graph/dictionary/whatever you want to archive implement NSCoding:
- (void)encodeWithCoder:(NSCoder*)coder {
[coder encodeObject:myIvar forKey:kmyIvar];
}
- (id)initWithCoder:(NSCoder*)coder {
if((self = [super initWithCoder:coder])) {
self.myIvar = [[coder decodeObjectForKey:kmyIvar] retain];
}
return self;
}
Check out this guide on core data. It's the best way to store data locally on your device. It's a native cocoa API and it is bindings compatible. Plus, you can choose whether to store data as XML, SQLite, and Binary.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html
For anything remotely large, I would use this.
I had this same question and just figured out a much better solution.
What you could do is just store the JSON string as your NSUserDefault. Then, when you reload the app use the same method (or framework utility) you used to map the JSON string to your objects the first time. This way you can still take advantage of the ease of NSUserDefaults.
If you're using RestKit to manage your web services it gets even easier. The answer on this post shows how to use RestKit's JSON parser to map from JSON to your object.
Deserializing local NSString of JSON into objects via RestKit (no network download)

How to access an EKCalendar's `account` property

Take a look at the documentation for EKCalendar. It's pretty simple, it has five properties, only one of which is a string called title. Now if you have multiple calendars on your iPhone and you open iCal's Calendar settings, you can see that all calendars are nicely grouped by another string called account.
What I can't figure out, is how to do the same, because although you can see the account when you NSLog a EKCalendar instance, you can't access it:
EKCalendar <0x1851b0> {title = Work; type = CalDAV; account = some#addr.ess; allowsModify = YES; color = 0.690196 0.152941 0.682353 1.000000}
There is no account property, and trying to access the valueForKey:#"account" isn't working either, unsurprisingly.
So how do I get to that account property? Such a simple thing, driving me nuts! Help is much appreciated.
Update: since iOS 5.0, EKCalendar has a source property.
If you use class-dump on the iOS 4.3 (Simulator) SDK, you'll see that there's a read-only method -(id)accountName. However, since it's not in the headers, it's unfortunately private API and you can't use it if you want your App to be accepted in the App Store.
I recommend that you file an enhancement request with Apple requesting that this method be made public.
Though thinking about it: If you're really desperate, why not parse the output of -[EKCalendar description]? It's very very very bad style and it'll probably break in the future, but you might make it through the App Store review ;) In particular if you only use it for grouping and write your code extremely defensive so it doesn't break but simply doesn't group, should the output of the description method be formatted differently.
This information is not available in the 4.0 SDK. Have you looked at the developer previews of the forthcoming SDK to see if that might contain more information?
And on iOS6? It seems the private property [EKCalendar accountName] vanished. And [EKCalendar description] does not contain account anymore. :
EKCalendar <0x200f50e0> {title = test#gmail.com; type = Exchange; allowsModify = YES; color = #44A703;}
The [EKCalendar source] provides a EKSource object, that has a title but this is not the name I typed when I created the account but seems to be a more generic name, e.g. Exchange, Other, CalDAV.

Cocoa Core data filename?

I followed Apple's example for creating a managed object which btw was great...
http://developer.apple.com/cocoa/coredatatutorial/index.html
However I now want to know what "name" (filename) the user saved his data as. Does anyone know how to pull the filename from the core data object.
something like this would be great...
NSLog (#"the filename is %#", [coreData filename]);
Any ideas?
Managed objects have no inherent relationship with files; there's nothing saying they ever need to go in to or come out of a file at all.
In the case that they are, you'll be wanting to look at the NSPersistentStore object eventually associated with your managed object through its managed object context and persistent store coordinator.