JSON-RPC call example

This example show how to make JSON-RPC call to Python.
The sample use the same example as before ‘add’ and will return 5.

UPDATE: Some examples that return different types.

use framework "Foundation"
use scripting additions

set theBody to "{
    \"jsonrpc\": \"2.0\",
    \"method\": \"dict\",
    \"params\": [],
    \"id\": 1
}"

(*
set theBody to "{
    \"jsonrpc\": \"2.0\",
    \"method\": \"array\",
    \"params\": [],
    \"id\": 1
}"
*)

(*
set theBody to "{
    \"jsonrpc\": \"2.0\",
    \"method\": \"string\",
    \"params\": [],
    \"id\": 1
}"
*)

(*
set theBody to "{
    \"jsonrpc\": \"2.0\",
    \"method\": \"integer\",
    \"params\": [],
    \"id\": 1
}"
*)

(*
set theBody to "{
    \"jsonrpc\": \"2.0\",
    \"method\": \"float\",
    \"params\": [],
    \"id\": 1
}"
*)

(*
set theBody to "{
    \"jsonrpc\": \"2.0\",
    \"method\": \"add\",
    \"params\": [2,3],
    \"id\": 1
}"
*)

set stringBody to current application's NSString's stringWithString:theBody
set postData to stringBody's dataUsingEncoding:(current application's NSUTF8StringEncoding)

its performJSONRPCRequestWithResponse("http://localhost:9070", postData)

on performJSONRPCRequestWithResponse(URLString, postData)
	set theRequest to its createRequestWithJSON(URLString, postData)
	set theData to its createResponseFromRequest(theRequest)
	set {theResult, theError} to current application's NSJSONSerialization's JSONObjectWithData:theData options:0 |error|:(reference)
	if theResult is missing value then error (theError's localizedDescription() as string)
	set returnObject to (theResult's valueForKey:"result") as anything
	if returnObject is missing value then
		return (theResult's valueForKey:"error") as anything
	else
		return returnObject
	end if
end performJSONRPCRequestWithResponse

on createRequestWithJSON(URLString, postData)
	set URLString to current application's |NSURL|'s URLWithString:URLString
	set theRequest to current application's NSMutableURLRequest's alloc()'s initWithURL:URLString
	theRequest's setHTTPMethod:"POST"
	theRequest's setHTTPBody:postData
	return theRequest
end createRequestWithJSON

on createResponseFromRequest(theRequest)
	set theResponse to current application's NSHTTPURLResponse's alloc()'s init()
	set {JSONData, theError} to current application's NSURLConnection's sendSynchronousRequest:theRequest ¬
		returningResponse:theResponse |error|:(reference)
	if JSONData is missing value then error (theError's localizedDescription() as string)
	-- debug the JSONData response
	log (current application's NSString's alloc()'s initWithData:JSONData ¬
		encoding:(current application's NSUTF8StringEncoding)) as string
	return JSONData
end createResponseFromRequest

Here is the server in Python, make sure you have the right module installed.
ex. jsonrpcserver The server use port 9070. Have been tested on Python 3.9
There is 2 method you could test ‘ping’ and ‘add’

Ps. I’m still investigate why I couldn’t get it to work in Twisted.

from http.server import BaseHTTPRequestHandler, HTTPServer
from jsonrpcserver import method, Result, Success, dispatch
import math
import datetime

@method
def dict() -> Result:
    object = {
        "name": "Johnny",
        "lastname": "Appleseed",
        "phone": "123.456.789",
        "math pi": math.pi,
        "date stamp": str(datetime.datetime.now()),
        "id": 12345678
    }
    return Success(object) 
    
@method
def array() -> Result:
    return Success([math.pi,"some string",[42,"other string"]])

@method
def string() -> Result:
    return Success("Some string")

@method
def integer() -> Result:
    return Success(42)

@method
def float() -> Result:
    return Success(math.pi)

@method
def ping() -> Result:
    return Success("pong")

@method
def add(x, y) -> Result:
    return Success(x + y)

class TestHttpServer(BaseHTTPRequestHandler):
    def do_POST(self):
        # Process request
        request = self.rfile.read(int(self.headers["Content-Length"])).decode()
        response = dispatch(request)
        # Return response
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(response.encode())


if __name__ == "__main__":
    HTTPServer(("localhost", 9070), TestHttpServer).serve_forever()

If you like to use a AppleScript record instead of JSON String.

Change this code:

set stringBody to current application's NSString's stringWithString:theBody
set postData to stringBody's dataUsingEncoding:(current application's NSUTF8StringEncoding)

To this, the key method is the method to use and key params is the parameters

set theBody to {jsonrpc:"2.0", method:"dict", params:{}, |id|:1}
set {postData, theError} to current application's NSJSONSerialization's ¬
	dataWithJSONObject:theBody options:0 |error|:(reference)

We could also send AppleScript record as parameter to Python with JSON-RPC.
It will be converted to Python dictionary. And if we later like to return a value
for a keyName with the right type.

set theBody to {jsonrpc:"2.0", method:"myObject", params:{{keyName:5}}, |id|:1}

And to return the value of the keyName

@method
def myObject(args) -> Result:
    object = args
    return Success(object['keyName'])

The parameter object is Python dictionary object. We return the value for the keyName.

Here is the implementation in Twisted module in Python to do JSON-RPC call.
You also need this: pip install txJSON-RPC

The example will add 2 numbers, the port is 7090

from txjsonrpc.web import jsonrpc
from twisted.web import server
from twisted.internet import reactor

class math_func(jsonrpc.JSONRPC):
    def jsonrpc_add(self, *args):
        """
        """
        r = args[0] + args[1]
        print(r'Result: ', r)
        return r

reactor.listenTCP(7090, server.Site(math_func()))
reactor.run()