POST JSON to Web service

Hi,
We have been given access to a web service that allows us to create jobs in SAP. It requires that we POST JSON to a given URL and the theory is that the result of the query is the Job Number of the newly created job.
I am new to ApplescriptObjC, but it seems this is achievable in Obj-C as I have been given a link to get me started:

http://stackoverflow.com/questions/7673127/how-to-send-post-and-get-request

I have been following a similar post at http://macscripter.net/viewtopic.php?id=33338, that appears to use the same terminology. I have butchered together something that does not error, but I don’t know if it worked either. It seems the critical bit is making the POST query and reading the result

property NSString : current application's class "NSString"
    property NSData : current application's class "NSData"
    property senduser : "test"
    property sendpass : "test"

on createJSON_(sender)
    --NSString *post = [NSString stringWithFormat:@"test=Message&this=isNotReal"]
    log 1
-- choose the JSON file
    set chosenFile to posix path of (choose file)
    set aURL to current application's |NSURL|'s fileURLWithPath:chosenFile
    set theJSONDoc to current application's NSXMLDocument's alloc()'s initWithContentsOfURL:aURL options:0 |error|:(missing value)
    log 2
-- expects it in text format
    set theJSONDoc to convertAnyToText(theJSONDoc)
    set post to NSString's stringWithFormat_(theJSONDoc)
    --NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    log 3
    set postData to post's dataUsingEncoding_allowLossyConversion_(1, true)
    --NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]]
    --set postLength to NSString's (postData's length)'s stringWithFormat_("%d")
    log 4
    tell current application's class "NSString" to set postLength to NSString's stringWithFormat_("%d", postdata's length)
    log 5
    --NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    --[request setURL:[NSURL URLWithString:@"http://YourURL.com/FakeURL"]];
    --[request setHTTPMethod:@"POST"];
    --[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    --[request setHTTPBody];
     tell current application's class "NSURL" to set theurl to its URLWithString_("http://" & senduser & ":" & sendpass & "@my.url.com")
     log 6
    tell current application's class "NSMutableURLRequest" to set request to its requestWithURL_cachePolicy_timeoutInterval_(theurl, 0, 60.0)
    request's setHTTPMethod_("POST")
    request's setValue_forHTTPHeaderField_(postLength, "Content-Length")
    request's setHTTPBody_(postData)
    log 7
    --NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    --[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    --NSString *requestReply = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
    --NSLog(@"requestReply: %@", requestReply);
    --}] resume]
    set session to current application's NSURLSession's sessionWithConfiguration:(missing value)
    session's setDelegate:me -- so script gets told when it's done
    set theTask to session's dataTaskWithRequest:request
    theTask's resume() -- start the task
    end

I can’t say I am proud of it, but it gets through from start to finish. The commented out bits are the original code in Obj-C
I think the trick is to figure out the response but that last NSURLSession bit is so nested and unfathomable to me I have no idea how to write it

If you can give any tips how to tidy up the above code, point out if I am going wrong somewhere, or unravel the NSURLSession command, I would be most grateful

Ian

A couple of points:

  • If you need JSON, you can’t use NSXMLDocument – that’s for XML. You use NSJSONSerialization for JSON. Without knowing what’s in the file, it’s hard to know what you need to do exactly.

  • You can’t use NSURLSession from AppleScriptObjC because it requires the use of blocks. You need to use its predecessor, NSURLConnection, instead.

  • If you’re not posting a lot of data, it might be easier to use NSURLConnection’s synchronous method -sendSynchronousRequest:returningResponse:error:. It blocks while waiting for a response, but you don’t have to worry about delegates and things – it’s a lot simpler. At the very least, it’s a good way to test if you have the request and data set up separately.

Thank you for your reply, Shane.

After a lot of trial and error, trying out your suggestions, this is where I am at:

set chosenFile to "/Users/ian/Desktop/test.json" --posix path of (choose file)
    set jSONDataString to (current application's NSString's alloc()'s initWithContentsOfFile:chosenFile)
    set jsonData to jSONDataString's dataUsingEncoding:(current application's NSUTF8StringEncoding)
    set aJsonDict to current application's NSJSONSerialization's JSONObjectWithData:jsonData options:0 |error|:(missing value)
    set {theData, theError} to current application's NSJSONSerialization's dataWithJSONObject:aJsonDict options:0 |error|:(reference)
    --set post to NSString's stringWithFormat_(theData)
    --set postData to post's dataUsingEncoding_allowLossyConversion_(1, true)
    --tell current application's class "NSString" to set postLength to NSString's stringWithFormat_("%d", postdata's length)
     tell current application's class "NSURL" to set theurl to its URLWithString_("http://" & senduser & ":" & sendpass & "@my.url.com")
    tell current application's class "NSMutableURLRequest" to set request to its requestWithURL_cachePolicy_timeoutInterval_(theurl, 0, 60.0)
    request's setHTTPMethod_("POST")
    --request's setValue_forHTTPHeaderField_(postLength, "Content-Length")
    request's setHTTPBody_(theData)
    set {foo, theResponse, theError} to current application's NSURLConnection's sendSynchronousRequest:request returningResponse:(reference) |error|:(reference)
    log theResponse

The header bit with the length etc produces an error so I have omitted it for now and I get a response from the web service. The response doesn’t contain anything I can use at the moment (including a status code: 500) so I will have to go back to the developer for the API documentation.

I am loading in the JSON from a file, but it could also be a string with some variables in it. It only has a handful of header and line item fields so is not large.

Should I be concerned that sendSynchronousRequest is deprecated?

You’re going in circles with the JSON stuff there. If you have a .json file, all you need is:

set chosenFile to "/Users/ian/Desktop/test.json" --posix path of (choose file)
set theData to current application's NSData's dataWithContentsOfFile:chosenFile

Not really. It’s discouraged because it blocks while the transfer happens, and developers are encouraged to use blocks or delegates to avoid that. But AppleScript can’t do blocks, and you generally don’t have complex UIs that freeze – AppleScript is generally designed around synchronous communication.

Try add ing this at the end:

log (theError's localizedDescription() as text)

Shane,

Well that simplifies the top section! As I was going down the NSJSONSerialization route, I was trying to wrangle the data into different JSON data types and probably got tied up in knots!

I get the error: missing value doesn’t understand the “localizedDescription” message. (error -1708)
Does this mean I am not actually getting an error?

Also, is there a project setting to always allow the keychain access, as I get a pop up warning each time

Cheers

That’s correct.It should probably be preceded by “if foo is missing value then”. You might want to log foo, too.

I suspect you’ll need to codesign it, but I’m honestly not sure.

Thanks for your help - I am a lot further forward than I was before