-
Feb 18th, 2018, 01:10 PM
#1
Thread Starter
Fanatic Member
[RESOLVED] Json
I'm trying to link my desktop application to an on-line appointment booking site. The API is about as clear as mud to me so I'm looking for bit of direction. Where the heck do I even start?
https://simplybook.me/en/api/develop...b/explorer_api
For example, they post this as sample code:
Code:
var loginClient = new JSONRpcClient({
'url': '//user-api.simplybook.me/login',
'onerror': function (error) {
alert(error);
}
});
var token = loginClient.getToken('mib', 'f43618e37b82004066d60db3431f4a06392599a6cfcafa8268bf25becc0ec7d7');
I'm assuming that's Java? Any way to translate that into VB?
I'm looking at this VB code but it's a bit unclear when I convert what info goes exactly where...
Code:
Set myMSXML = CreateObject("Microsoft.XmlHttp")
myMSXML.open "POST", "http://....", False
myMSXML.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
myMSXML.setRequestHeader "User-Agent", "Firefox 3.6.4"
myMSXML.send "param1=value2¶m2=value2"
MsgBox myMSXML.responseText
Any ideas???
Many thanks in advance for your help and sorry for the noob question.
Last edited by Darkbob; Feb 18th, 2018 at 01:30 PM.
-
Feb 18th, 2018, 01:53 PM
#2
Re: Json
This is a fail out of the gate:
Code:
Set myMSXML = CreateObject("Microsoft.XmlHttp")
You will be fighting the UrlMon cache. Use WinHTTP instead for web service calls.
-
Feb 18th, 2018, 02:50 PM
#3
Thread Starter
Fanatic Member
Re: Json
Originally Posted by dilettante
This is a fail out of the gate:
Code:
Set myMSXML = CreateObject("Microsoft.XmlHttp")
You will be fighting the UrlMon cache. Use WinHTTP instead for web service calls.
Good tip. By the way I've been making really decent progress. I made a little HTM file on my desktop and then used the Developer tools in Chrome to see the responses. After that I was able to break down the communication and put it over to the hurl.it web site to test.
Final step will be to convert the request generated in hurl.it to VB6.
Complex process but it's coming together - this is what I have so far:
Code:
Private Sub Command_Click()
Dim Q As String
Q = Chr$(34)
Dim Company$
Dim AuthKey$
Dim CompanyID$
Dim Payload$
Company$ = "MyCompany"
AuthKey$ = "MyAuthKey"
CompanyID$ = "1"
Set oXMLHTTP = New WinHttp.WinHttpRequest
Payload$ = "{" & Q & "jsonrpc" & Q & ":" & Q & "2.0" & Q & "," & Q & "method" & Q & ":" & Q & "getToken" & Q & "," & Q & "params" & Q & ":[" & Q
Payload$ = Payload$ & Company$ & Q & "," & Q & AuthKey$
Payload$ = Payload$ & Q & "]," & Q & "id" & Q & ":" & CompanyID$ & "}"
PayloadLength$ = Trim(Str(Len(Payload$)))
Call oXMLHTTP.Open("POST", "http://user-api.simplybook.me/login", False)
Call oXMLHTTP.SetRequestHeader("Accept", "application/json, text/javascript, */*; q=0.01")
Call oXMLHTTP.SetRequestHeader("Accept-Encoding", "gzip, deflate")
Call oXMLHTTP.SetRequestHeader("Accept-Language", "en-US,en;q=0.9")
Call oXMLHTTP.SetRequestHeader("Connection", "keep-alive")
Call oXMLHTTP.SetRequestHeader("Content-Length", Q & PayloadLength$ & Q)
Call oXMLHTTP.SetRequestHeader("Content-Type", "application/json")
Call oXMLHTTP.SetRequestHeader("DNT", "1")
Call oXMLHTTP.SetRequestHeader("Host", "user-api.simplybook.me")
Call oXMLHTTP.Send Payload$
Response$ = oXMLHTTP.responsetext
MsgBox Response$
End Sub
So now that I've got my response back from the site I need to grab the token out of that string and move on to the next step - booking and retrieving bookings from the site. But it's coming really well. So thanks for the tip!
Last edited by Darkbob; Feb 18th, 2018 at 03:53 PM.
-
Feb 18th, 2018, 03:58 PM
#4
Re: Json
Originally Posted by Darkbob
Good tip. By the way I've been making really decent progress. I made a little HTM file on my desktop and then used the Developer tools in Chrome to see the responses. After that I was able to break down the communication and put it over to the hurl.it web site to test.
Final step will be to convert the request generated in hurl.it to VB6.
Complex process but it's coming together.
It will not be as complex, as soon as you have a generic Helper-Function in place, which for that "SimplyBook"-API would be:
(preferrably placed as "shared code" in a *.bas-module):
Code:
Option Explicit
Public Function JSONRPC(URL$, MethodName$, ParamArray P()) As String
Static id As Currency: id = id + 1
Dim http As New WinHttp.WinHttpRequest, JSONPostBody$(0 To 3), i&
http.Open "POST", URL, False
http.SetRequestHeader "Content-Type", "application/json; charset=UTF-8"
http.SetRequestHeader "Accept", "application/json"
JSONPostBody(0) = """jsonrpc"":""2.0"""
JSONPostBody(1) = """method"":""" & MethodName & """"
JSONPostBody(2) = """id"":" & id
JSONPostBody(3) = """params"":" & MakeJSONArrayFromParams(P)
http.Send "{" & Join(JSONPostBody, ",") & "}"
JSONRPC = http.ResponseText
End Function
Private Function MakeJSONArrayFromParams(ByVal PArr) As String 'Helper-function for the above main-request-function
Dim Tmp$(), P
Tmp = Split(vbNullString)
For Each P In PArr
ReDim Preserve Tmp(0 To UBound(Tmp) + 1)
Select Case VarType(P)
Case vbString: Tmp(UBound(Tmp)) = """" & P & """"
Case vbBoolean: Tmp(UBound(Tmp)) = IIf(P, "true", "false")
Case vbEmpty, vbNull: Tmp(UBound(Tmp)) = "null"
Case Else: Tmp(UBound(Tmp)) = Str$(P)
End Select
Next
MakeJSONArrayFromParams = "[" & Join(Tmp, ",") & "]"
End Function
Note, that the above is doing (for demonstrations sake) the JSON-serialization "by hand" (in a simplified, naive manner)...
You will be better off, when you use decent JSON-classes you can then simply "add to" (Key- and Value-wise) -
and then use the Serialization-functions of these JSON-Helpers to provide the Body-Content for your POST-request...
Anyways, with something "generic" as the above in place (in a *.bas module), you can encapsulate each "API-EndPoint" (each unique ServiceURL)
behind their own dedicated Classes, as e.g. for the Login-API-Endpoint (ServiceURL: 'https://user-api.simplybook.me/login') -
you could write this simple Class-based wrapper-implementation now (name it e.g. cSimplyBookLogin)
Code:
Option Explicit
Private Const EndPoint$ = "https://user-api.simplybook.me/login" '<- all method-impl. below share the same Service-URL
Public Function getServiceUrl(companyLogin$) As String
getServiceUrl = JSONRPC(EndPoint, "getServiceUrl", companyLogin)
End Function
Public Function getToken(companyLogin$, apiKey$) As String
getToken = JSONRPC(EndPoint, "getToken", companyLogin, apiKey)
End Function
Public Function getUserToken(companyLogin$, userLogin$, userPassword$) As String
getUserToken = JSONRPC(EndPoint, "getUserToken", companyLogin, userLogin, userPassword)
End Function
Public Function getApplicationToken(applicationApiKey$) As String
getApplicationToken = JSONRPC(EndPoint, "getApplicationToken", applicationApiKey)
End Function
Usage then:
Code:
Option Explicit
Private Sub Form_Load()
With New cSimplyBookLogin
Debug.Print .getToken("mib", "f43618e37b82004066d60db3431f4a06392599a6cfcafa8268bf25becc0ec7d7")
End With
End Sub
Olaf
-
Feb 18th, 2018, 04:11 PM
#5
Thread Starter
Fanatic Member
Re: Json
That is amazingly helpful! Thanks!!
I had to look up how to use a Class in VB. Hard to believe I've been using VB6 since it was released and I've never used Classes. But super cool and easy to do.
By the way your code worked perfectly. I put in the Module and Class as you suggested, did a quick cut and paste and now my code looks like this:
.getToken(Company$, AuthKey$)
WAY easier. Thanks.
Last edited by Darkbob; Feb 18th, 2018 at 04:28 PM.
-
Feb 18th, 2018, 05:22 PM
#6
Thread Starter
Fanatic Member
Re: Json
Hey Schmidt. Looks like I declared success a bit too quickly. I managed to parse out the token from the result string so that was progress.
The API shows the 2nd step is Geteventlist.
When I tried to use your fancy Class stuff I got DENIED.
I adjusted the URL to remove the /login because it's not necessary for step 2. It looks like getEventsList doesn't need params but when I traced through the Class code you posted I see it's building a params list...
Any ideas?
-
Feb 18th, 2018, 08:15 PM
#7
Re: Json
Originally Posted by Darkbob
Hey Schmidt. Looks like I declared success a bit too quickly. I managed to parse out the token from the result string so that was progress.
There is no expensive parsing needed, as soon as you use decent JSON-HelperObjects (e.g. those available in vbRichClient5).
The code I've posted was not thought as a "solution" - only as a rough hint to get you started...
Here is the global JSON-Helper-Routine again (which should be placed in a *.bas),
but already using these JSON-HelperObjects (the project needs a reference to vbRichCLient5 now)
Oh, and note the magenta-colored sections, which are responsible to set the Extra-Headers which are needed after Token-retrieval.
Code:
Option Explicit
Public glbCompanyLogin As String, glbToken As String
Public Function JSONRPC(URL$, MethodName$, ParamArray Params()) As String
Static id As Currency: id = id + 1
Dim http As New WinHttp.WinHttpRequest, JSONBody As cCollection, JSONParams As cCollection, P
http.Open "POST", URL, False
http.SetRequestHeader "Content-Type", "application/json; charset=UTF-8"
http.SetRequestHeader "Accept", "application/json"
If Len(glbCompanyLogin) Then http.SetRequestHeader "X-Company-Login", glbCompanyLogin
If Len(glbToken) Then http.SetRequestHeader "X-Token", glbToken
Set JSONParams = New_c.JSONArray
For Each P In Params: JSONParams.Add P: Next
Set JSONBody = New_c.JSONObject
JSONBody.Prop("jsonrpc") = "2.0"
JSONBody.Prop("method") = MethodName
JSONBody.Prop("id") = id
JSONBody.Prop("params") = JSONParams
http.Send JSONBody.SerializeToJSONUTF8
JSONRPC = http.ResponseText
End Function
Originally Posted by Darkbob
The API shows the 2nd step is Geteventlist.
When I tried to use your fancy Class stuff I got DENIED.
I adjusted the URL to remove the /login because it's not necessary for step 2. It looks like getEventsList doesn't need params but when I traced through the Class code you posted I see it's building a params list...
The "removal of /login" should already have suggested to you: "stop, let's write a new, separate class for that new EndPoint-Root-URL".
Here is this new Class, which reflects the first 2 - and also the last Method in that "NameSpace" described here: https://simplybook.me/api/developer-...nitListdefault
You should name this Class cSimplyBook (note, that all results are now returned as JSON-Collection-Objects):
Code:
Option Explicit 'the Main-Service-URL and its Methods
Private Const EndPoint$ = "https://user-api.simplybook.me" '<- all method-impl. below share the same Service-URL
Public Enum enmClassHandling
WithoutClasses = -1
SkipClasses = 0
OnlyClasses = 1
End Enum
'The first two API-implementations from the list given here: https://simplybook.me/api/developer-api/tab/doc_api#getUnitListdefault
Function getUnitList(ByVal isVisibleOnly As Boolean, ByVal asArray As Boolean, ByVal handleClasses As enmClassHandling) As cCollection
Set getUnitList = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getUnitList", isVisibleOnly, asArray, handleClasses))
End Function
Function getEventList(ByVal isVisibleOnly As Boolean, ByVal asArray As Boolean, ByVal handleClasses As enmClassHandling) As cCollection
Set getEventList = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getEventList", isVisibleOnly, asArray, handleClasses))
End Function
'....more methods in analogy to what's already contained here (in case the method is availalbe behind the same Endpoint-URL)
'and the last API-Method in the Main-ServiceURL (the Root-EndPoint as defined as a Const above: https://user-api.simplybook.me)
Function getCompanyTimezoneOffset() As cCollection
Set getCompanyTimezoneOffset = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getCompanyTimezoneOffset"))
End Function
And the already posted Class cSimplyBookLogin is now also adapted to the new requirements:
Code:
Option Explicit
Private Const EndPoint$ = "https://user-api.simplybook.me/login" '<- all method-impl. below share the same Service-URL
Public Function getServiceUrl(companyLogin$) As String
getServiceUrl = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getServiceUrl", companyLogin)).Prop("result")
glbCompanyLogin = companyLogin 'set the global CompanyString-Variable (which is later incorporated into the POST-request-headers)
End Function
Public Function getToken(companyLogin$, apiKey$) As cCollection
Set getToken = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getToken", companyLogin, apiKey))
glbToken = getToken.Prop("result") 'set the global TokenString-Variable (which is later incorporated into the POST-request-headers)
glbCompanyLogin = companyLogin 'set the global CompanyString-Variable (which is later incorporated into the POST-request-headers)
End Function
Public Function getUserToken(companyLogin$, userLogin$, userPassword$) As cCollection
Set getUserToken = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getUserToken", companyLogin, userLogin, userPassword))
glbCompanyLogin = companyLogin 'set the global CompanyString-Variable (which is later incorporated into the POST-request-headers)
End Function
Public Function getApplicationToken(applicationApiKey$) As cCollection
Set getApplicationToken = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getApplicationToken", applicationApiKey))
End Function
Finally the updated Form-Code ( which now also requires an MSHFlexGrid on your Form, to visualize the returned JSON-Arrays)
Code:
Option Explicit
Private Login As New cSimplyBookLogin, SBook As New cSimplyBook
Private Sub Form_Load()
With MSHFlexGrid1
.Rows = 2: .Font.Name = "Arial"
.ColWidth(0) = 2000: .TextMatrix(0, 0) = "Keys"
.ColWidth(1) = 8000: .TextMatrix(0, 1) = "Values"
.ColAlignment(1) = 1
End With
Debug.Print Login.getToken("mib", "f43618e37b82004066d60db3431f4a06392599a6cfcafa8268bf25becc0ec7d7").Prop("result")
EnumerateEventPropsFor SBook.getCompanyTimezoneOffset().Prop("result")
Dim EvtList As cCollection
Set EvtList = SBook.getEventList(True, True, SkipClasses).Prop("result")
Dim Evt As cCollection
For Each Evt In EvtList
EnumerateEventPropsFor Evt
Next
End Sub
Private Sub EnumerateEventPropsFor(Evt As cCollection)
Dim R As Long, i As Long
R = MSHFlexGrid1.Rows
MSHFlexGrid1.Rows = R + Evt.Count + 1
For i = 0 To Evt.Count - 1
KeyValueToGrid R + i, Evt.KeyByIndex(i), Evt.ItemByIndex(i)
Next
End Sub
Private Sub KeyValueToGrid(RowIdx, Key, Value)
MSHFlexGrid1.TextMatrix(RowIdx, 0) = Key
If IsObject(Value) Then
MSHFlexGrid1.TextMatrix(RowIdx, 1) = Value.SerializeToJSONString
Else
MSHFlexGrid1.TextMatrix(RowIdx, 1) = Value
End If
End Sub
Here the output of all of the above:
HTH
Olaf
-
Feb 18th, 2018, 10:18 PM
#8
Thread Starter
Fanatic Member
Re: Json
First off, this is genius level code and way above my skill level. For sure it's the best answer to any question I've ever posted.
Initially I had a bug but only because I missed a copy/paste for the line: Public glbCompanyLogin As String, glbToken As String
But all fixed and working perfectly!
Thanks so much.
Last edited by Darkbob; Feb 18th, 2018 at 10:30 PM.
-
Feb 19th, 2018, 01:20 PM
#9
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
Ok... lol.. marked it as resolved. I should never do that because it seems there's always one more thing.
Now I'm trying to get a listing of bookings and later I'll want to mark spots on the calendar as they are booked in the office.
To start, I added a really simple call to "getBookings" without any parameters. That should return everything in the calendar. If I get this working I'll want to put in a start and end date as well as a provider so I can download the list of bookings.
Code:
Function getBookings(ByVal isVisibleOnly As Boolean, ByVal asArray As Boolean, ByVal handleClasses As enmClassHandling) As cCollection
' support says to make a call like this:
'{"jsonrpc":"2.0","method":"getBookings","id":"1","params":[{"date_from":"2017-01-01","date_to":"2017-12-12"}]}
' But to start for simplicity, I'm leaving off the dates
Set getBookings = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getBookings"))
End Function
The response back from the server was:
{"error":{"code":-32601,"message":"Method not found","data":null},"id":"2","jsonrpc":"2.0"}
I changed my call to:
Set getBookings = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getBookings", "date_from", "2010-01-01", "date_to", "2018-02-19"))
Same result back.
Any ideas or are you sick of me yet?
Last edited by Darkbob; Feb 19th, 2018 at 01:32 PM.
-
Feb 19th, 2018, 03:51 PM
#10
Re: [RESOLVED] Json
Originally Posted by Darkbob
Ok... lol.. marked it as resolved. I should never do that because it seems there's always one more thing.
Now I'm trying to get a listing of bookings and later I'll want to mark spots on the calendar as they are booked in the office.
To start, I added a really simple call to "getBookings" without any parameters.
You should work tightly with the API-descriptions on that site:
https://simplybook.me/api/developer-...gsdefaultadmin
And the "getBookings"-Method is clearly flagged (if you scroll up a bit), as available behind a "new, different EndPoint-URL":
- 'https://user-api.simplybook.me/admin'
Please look at these Endpoint-URLs as "NameSpaces" (which are separated for a reason)...
In my little Demo above I've implemented:
- (partially) the "Root-Namespace": cSimplyBook -> Const EndPoint$ = 'https://user-api.simplybook.me'
- (fully) the "Login-SubNamespace": cSimplyBookLogin -> Const EndPoint$ = 'https://user-api.simplybook.me/login'
And since your new method "getBookings" is located behind the EndPoint: 'https://user-api.simplybook.me/admin',
it should be quite obvious, what you have to do now:
- write a new Class for it, called cSimplyBookAdmin -> Const EndPoint$ = 'https://user-api.simplybook.me/admin'
Then start implementing that new "NameSpace-Class" by applying the same "basic-principles" as in all the other classes.
I guess, now is a good time for you, to "rise and shine"
(implementing your getBookings within that new Class yourself)...
Olaf
-
Feb 19th, 2018, 05:57 PM
#11
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
Good tips! I got my new class added. Got my namespace set. Made my new getBookings function. End result?
{"error":{"code":-32600,"message":"Access denied","data":null},"id":"6","jsonrpc":"2.0"}
Not sure if that's progress or not... but at least it's different!
I think the problem might be that you need to log in with the User information and user password as opposed to get getting a company token.
Code:
var loginClient = new JSONRpcClient({
'url': 'https://user-api.simplybook.me' + '/login',
'onerror': function (error) {},
});
var token = loginClient.getUserToken(YOUR_COMPANY_LOGIN, YOUR_USER_LOGIN, YOUR_USER_PASSWORD);
Wow this stuff is complex...
Last edited by Darkbob; Feb 19th, 2018 at 06:05 PM.
-
Feb 19th, 2018, 06:13 PM
#12
Re: [RESOLVED] Json
Originally Posted by Darkbob
{"error":{"code":-32600,"message":"Access denied","data":null},"id":"6","jsonrpc":"2.0"}
Not sure if that's progress or not... but at least it's different!
I think the problem might be that you need to log in with the User information and user password as opposed to get getting a company token.
var token = loginClient.getUserToken(YOUR_COMPANY_LOGIN, YOUR_USER_LOGIN, YOUR_USER_PASSWORD);[/CODE]
Yep, and the approriate method is already implemented in cSimplyBookLogin.
(though perhaps this method will not succeed with the "generic test-company-credentials" I was using so far with the getToken-method...,
time to register for a real App-account with the service-provider, to be able to feed "valid and correct params" into getUserToken(...))
Olaf
-
Feb 19th, 2018, 07:10 PM
#13
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
So close I can almost taste it. I have an account on the site already (they are free). I made a glbUserToken variable and used the .getUserToken method you have already coded to save it. I was able to get a user token just fine. So now I've got two tokens - ever so slightly different from each other.
After getting the user token, I just set the glbToken = glbUserToken hoping that would work as a test proof of concept but I was still denied.
I probably have to pass the company name, user name and/or user token somehow. What a huge pain.
-
Feb 20th, 2018, 09:59 AM
#14
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
Nope. No luck at all. So close but I'm stuck. If it's against site rules to offer a job I'm sorry but I'd be more than willing to hire somebody with the skills to get this going. Please PM me if you think you are up to the job.
-
Feb 20th, 2018, 11:30 AM
#15
Re: [RESOLVED] Json
Originally Posted by Darkbob
Nope. No luck at all. So close but I'm stuck. If it's against site rules to offer a job I'm sorry but I'd be more than willing to hire somebody with the skills to get this going. Please PM me if you think you are up to the job.
According to what's written on this page:
https://simplybook.me/api/developer-api/tab/guide_api
(under section:
User/Admin API
(when you scroll down a bit)...
You will apparently have to set an additional Header-Field in the JSONRequest-Routine
If Len(glbUserToken) Then http.SetRequestHeader "X-User-Token", glbUserToken
I've marked these Extra-Header-Fields (as the place where the glbVariables shall be applied)
in post #7 in magenta.
HTH
Olaf
-
Feb 20th, 2018, 11:49 AM
#16
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
Bingo! I'm authorized now. It worked! You're a genius.
I guess it's a bit ugly but I changed your code like this:
Code:
If Right$(URL$, 5) = "admin" Then
If Len(glbToken) Then
http.SetRequestHeader "X-User-Token", glbUserToken
End If
Else
If Len(glbToken) Then
http.SetRequestHeader "X-Token", glbToken
End If
End If
First I tried this:
Code:
Set getBookings = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getBookings"))
I thought i could pass no parameters and get a response. But it failed.
{"error":{"code":-32011,"message":"Params is not array","data":[]},"id":"7","jsonrpc":"2.0"}
Next I sent this line to my getBookings
Code:
Set getBookings = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "getBookings", "date_from", "2017-01-01", "date_to", "2018-02-20"))
I got back a response - better than DENIED but still not quite there...
{"error":{"code":-32011,"message":"Params is not array","data":[]},"id":"7","jsonrpc":"2.0"}
I wonder if it needs a bracket [ around the passed parameters?
-
Feb 20th, 2018, 12:29 PM
#17
Re: [RESOLVED] Json
Originally Posted by Darkbob
Bingo! I'm authorized now.
...
I got back a response - better than DENIED but still not quite there...
{"error":{"code":-32011,"message":"Params is not array","data":[]},"id":"7","jsonrpc":"2.0"}
I wonder if it needs a bracket [ around the passed parameters?
From the Parameter-example you need to ensure (according to the help-site here: https://help.simplybook.me/index.php...ds#getBookings )
that it is a single Parameter, but in form of a JSON-Object (which you can detect by the enclosing curly braces {...} ):
Code:
{
"date_from":"2015-12-29",
"date_to":"2015-12-29",
"booking_type":"cancelled",
"event_id":"5",
"order":"start_date"
}
You can build such a single JSON-Object-Param this way:
Code:
Dim JSONObj as cCollection
Set JSONObj = New_c.JSONObject
JSONObj.Prop("date_from") = "2018-02-19"
JSONObj.Prop("date_to") = "2018-02-19"
JSONObj.Prop("booking_type") = "cancelled"
Debug.Print SimplyAdmin.getBookings(JSONObj).SerializeToJSONString
And then pass it to this accordingly defined function:
Code:
Function getBookings( JSONObj As cCollection ) As cCollection
Set getBookings = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, JSONObj))
End Function
HTH
Olaf
-
Feb 21st, 2018, 12:13 PM
#18
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
OMG I frickin GOT IT WORKING! I had to muck with the getBookings a bit. This line needs a method.
Set getBookings = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, JSONObj))
but that was very easy to add once I figured out what was going on.
But I got it working.... I'm amazed by the time you have put into helping a stranger get something like this going. Thank you very much for all your patience. This is all a very steep learning curve for me but you held my hand every step of the way.
Of course the next challenge will be to get the BOOK function going so I can lock out spots that are already used. That looks very challenging so I'm sure I'll be back for more spoon feeding.
I can't imagine people are born knowing this stuff. How on earth did you learn it? Are there any resources (books, sites, classes?) you could recommend?
-
Feb 21st, 2018, 07:45 PM
#19
Re: [RESOLVED] Json
Originally Posted by Darkbob
OMG I frickin GOT IT WORKING!...
Glad you did (on your own this time)...
Originally Posted by Darkbob
I can't imagine people are born knowing this stuff.
Of course not...
Originally Posted by Darkbob
How on earth did you learn it? Are there any resources (books, sites, classes?) you could recommend?
Less by books, more by reading "current developer-journals" and "online articles" or "online specs" (as e.g. for the JSON-format)...
But the main-thing what normally should drive any nerd anywhere is "sheer obstinacy",
with the occasional reward you already decribed so felicitously above:
"OMG I frickin GOT IT WORKING!..."
Olaf
-
Feb 22nd, 2018, 04:54 PM
#20
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
And we're back... lol...
So now it's time to do the Book api. I've got a desktop app with appointment slots. I've got a web site on SimplyBook.me. The web site shows 9:00 am and 9:15am are wide open and able to take new appointments.
Customer calls the office directly and books 9:15 am so it's no longer available.
Now I have to somehow tell the web site that the 9:15am spot is no longer available. I'm thinking that if I book anybody in that spot it should show as full on the web site so a dummy person would do the trick.
But how? The API says: book ($eventId, $unitId, $date, $time, $clientData, , $count, $batchId, $recurringData)
@param Integer $eventId
@param Integer $unitId
@param String $date in Y-m-d format
@param String $time in H:i:s format
@param Object $clientData eg. {name: 'Name', email: 'test@gmail.com', phone: '+38099999999'}
@param array
@param Integer $count bookings count, min. 1 (using for group bookings batch). (optional)
@param Integer $batchId add booking to multiple bookings batch. (optional)
@param Array $recurringData make booking recurrent. (optional)
@return Object
Basically I have to pass three different kinds of things. One type would be an Integer or string. One would be an Object. One would be an optional array.
So I'm thinking my call might look something like this:
bookASpot = JSONRPC(EndPoint, "book", eventId,unitId,bookdate,booktime...
Now I get a bit hazy. I know I need to pass along an object and/or collection. And I sort of know how to build one.
For the $clientData object I could use something like this as an example:
'Object $clientData eg. {name: 'Name', email: 'test@gmail.com', phone: '+38099999999'}
Set JSONObj = New_c.JSONObject
JSONObj.Prop("name") = "John Doe"
JSONObj.Prop("email") = "test@gmail.com"
JSONObj.Prop("phone") = "+14035551212"
So...
ClientID = JSONObj.SerializeToJSONString
bookASpot = JSONRPC(EndPoint, "book", eventId,unitId,bookdate,booktime,ClientID...
Or something like that???
-
Feb 22nd, 2018, 09:03 PM
#21
Re: [RESOLVED] Json
Originally Posted by Darkbob
And we're back... lol...
So now it's time to do the Book api.
I can help you "formalize" the Method-Signature for the book-Method (in your cSimplyBook-Class),
but not much further (since I have not the time for fiddling out the details with regards to e.g.:
- the format of "time-slices"
- or to verify, whether the API-call in question indeed produced the correct result
And just to ensure that we are "on the same page" - I'd like you to look at the API-descriptions on:
https://help.simplybook.me/index.php
(since they are more "formal" and "complete" IMO on that page) - so for the book-method, we should both look at:
https://help.simplybook.me/index.php...e_methods#book
Parameters:
- $eventId Integer
- $unitId Integer
- $date String - in Y-m-d format
- $time String - in H:i:s format
- $clientData Object - eg. {name: 'Name', email: 'test@gmail.com', phone: '+38099999999'}
- $additional array|Object - additional params and fields.
- $count Integer - bookings count, min. 1 (using for group bookings batch). (optional)
- $batchId Integer - add booking to multiple bookings batch. (optional)
- $recurringData Array - make booking recurrent. (optional)
Returns Object.
You can (and should) translate this Param-List quite "literally" to the VB6-Method in your appropriate "EndPoint-Class" like:
Code:
Public Function book( _
ByVal eventId As Long, _
ByVal unitId As Long, _
ByRef sdate As String, _
ByRef stime As String, _
ByRef clientData As cCollection, _
Optional ByRef additional As cCollection, _
Optional ByVal count As Long, _
Optional ByVal batchId As Long, _
Optional ByRef recurringData As cCollection _
) As cCollection
End Function
So you see, that you can translate their API-definitions quite directly to VB6, keeping in mind that:
- "Integer" type translates to "ByVal"-Longs
- "String" remains String (Dates are given as ISO-Date-Strings, ISO-time or combined ISO-DateTime-strings as e.g. '2018-02-23 23:15:30'
- Object or Array both translate to cCollection (since a cCollection can hold both of these "JSON-NodeTypes", you can verify what it is via cCollection.IsJSONArray or .IsJSONObject
As for the Optional Params above (additional, count, batchId, recurringData):
- make sure (inside the above VB-method), that you "either fill them with a decent default" (in case they were not passed on the VB-caller-side)
- or simply "don't pass them along" (into the final JSONRPC-call)
Here the implementation (with the appropriate "validation-checks"):
Code:
Public Function book( _
ByVal eventId As Long, _
ByVal unitId As Long, _
ByRef sdate As String, _
ByRef stime As String, _
ByRef clientData As cCollection, _
Optional ByRef additional As cCollection, _
Optional ByVal count As Long, _
Optional ByVal batchId As Long, _
Optional ByRef recurringData As cCollection _
) As cCollection
If clientData Is Nothing Then Err.Raise vbObjectError, , "clientData may not be nothing"
If Not clientData.IsJSONObject Then Err.Raise vbObjectError, , "clientData needs to be passed as a JSON-Object"
If additional Is Nothing Then Set additional = New_c.JSONObject 'ensure an empty JSON-Object
If count = 0 Then 'the optional count-Param was left out, so it is not a batch-call, so we leave the last 3 Params out in the RPC-call below
Set book = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "book", eventId, unitId, sdate, stime, clientData, additional))
Else 'it is a Batch-call, so all 3 of the remaining optional Params need to be passed (and be valid)
If recurringData Is Nothing Then Err.Raise vbObjectError, , "recurringData may not be nothing"
If Not recurringData.IsJSONArray Then Err.Raise vbObjectError, , "recurringData needs to be passed as a JSON-Array"
Set book = New_c.JSONDecodeToCollection(JSONRPC(EndPoint, "book", eventId, unitId, sdate, stime, clientData, additional, count, batchId, recurringData))
End If
End Function
How to properly fill the arguments which are of type "cCollection" (which as said, can either be a JSON-Object or a JSON-array)
on the outside (from your UserCode) is up to you...
E.g. you could write a "Helper-class" cClientData to build (and later hold) client-relevant Data,
which you filled in from a DB for example (for multiple Clients) - and if you want to pass such a cClientData-instance along into the above defined Method,
you simply have to ensure a "GetJSONObject"-method inside your cClientData-Class, which builds such a thing:
Code:
Option Explicit 'a cClientData-Class Helper
Public Name As String, Email As String, Phone As String
Public Sub Init(Name As String, Email As String, Phone As String)
Me.Name = Name
Me.Email = Email
Me.Phone = Phone
End Sub
Public Function GetJSONObject() As cCollection
Set GetJSONObject = new_c.JSONObject
GetJSONObject .Prop("name") = Name
GetJSONObject .Prop("email") = Email
GetJSONObject .Prop("phone") = Phone
End Function
You can then fill such a little cClientData Class-Instance by e.g. using the Init-Method (to do it in one line) -
or by setting the Public Properties "Name, Email, Phone" directly - even store multiple "set-up" client-instances in your own VB-Collection or something...
If it comes to passing these Client-Instances to the book-API-method, you will then not pass
the cClientData-reference directly, but instead:
Code:
Dim oClientData As New cClientData
oClientData.Init "Fred", "a@b.com", "1234567"
Set result = SimplyBook.book(eventId, unitId, sdate, stime, oClientData.GetJSONObject, ...)
HTH
Olaf
-
Feb 23rd, 2018, 11:17 AM
#22
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
That's some really detailed "hints"! You should seriously work for SimplyBook as Tier 2 tech support. Any time I contact them they just reply with a one line cut/paste to the docs. Your explanation is very detailed and really helps. I'll get on it this weekend and see what I can come up with. Thanks again!!
PS I managed to get it working! Because I turned on a feature requiring client log-in I had to use the admin api rather than the public api but same basic idea. It needs a client ID rather than name and phone number. I just went to the web site and made a dummy client named "Booked". Then I pass the client ID and a few time parameters and the spot disappears from the web site. Easy as can be. Lol.... well easy with a ton of help!! I can't even begin to tell you how much help this has been. If you ever need anything - kidney... whatever just let me know!
Last edited by Darkbob; Feb 23rd, 2018 at 02:47 PM.
-
Feb 23rd, 2018, 03:54 PM
#23
Thread Starter
Fanatic Member
Re: [RESOLVED] Json
OK... last one I promise (for today )
The result after booking is coming back as a very complex object.
I used this:
Code:
Set EvtList = book(eventId, unitId, ClientID, SDATE, Stime, EDate, Etime, clientTimeOffset).Prop("result")
Tracing through I found the raw HTML Response was this:
{"result":{"require_confirm":false,"bookings":[{"id":"16","event_id":1,"unit_id":1,"client_id":4,"start_date_time":"2018-02-23 15:15:00","end_date_time":"2018-02-23 15:30:00","time_offset":"0","is_confirmed":"1","require_payment":false,"code":"sd8fggn","hash":"2ade 3784471cb9509d22d85ffbc60682"}]},"id":"5","jsonrpc":"2.0"}
evtlist.count was 2
evtlist.IsJSONObject was True
But
Code:
For Each Evt In EvtList
EnumerateEventPropsFor Evt
Next
produced an error saying Object Required.
This code:
Code:
EnumerateEventPropsFor Evtlist
Produced a reasonable output except there was one key for "bookings" with a long string in it:
{"id":"16","event_id":1,"unit_id":1,"client_id":4,"start_date_time":"2018-02-23 15:15:00","end_date_time":"2018-02-23 15:30:00","time_offset":"0","is_confirmed":"1","require_payment":false,"code":"sd8fggn","hash":"2ade 3784471cb9509d22d85ffbc60682"}]},"id":"5","jsonrpc":"2.0"
I guess I'm not really clear on how to break out a complex result JSON object.
In the end I just stored the response string in a Global Variable called JString$ then added this code:
Code:
Call book(eventId, unitId, ClientID, SDATE, Stime, EDate, Etime, clientTimeOffset)
If InStr(JString$, "is_confirmed")>0 Then
MsgBox "Confirmed"
ElseIf InStr(JString$, "error") > 0 Then
MsgBox "Failed"
Else
MsgBox "Can neither confirm nor deny"
End If
It's ugly but it works. If you can see something I'm doing wrong with the EvtList parsing please let me know! And thanks again.
Last edited by Darkbob; Feb 23rd, 2018 at 04:29 PM.
-
Feb 24th, 2018, 01:58 AM
#24
Re: [RESOLVED] Json
Originally Posted by Darkbob
The result after booking is coming back as a very complex object.
You have to get more "familiar" with JSON I guess (reading and "googling" about it).
The format is hierarchical and can be nested quite deeply...
If the returned JSON is a JSON-Array, you can access its member-values either via:
- "For Each Enumeration"
- or alternatively (after asking for its JSONResult.Count) directly by "ZeroBased Index" - as e.g. JSONResult(0) or JSONResult(1)
If the returned JSON is a JSON-Object, you can access its Members either via:
- "For Each Enumeration" (but then you will only get the Values, not the Keys associated with these Values)
- or by "direct access via StringKey" (to retrieve the single Value which sits behind this Key
- or by Enumeration via ZeroBasedIndex again (For i = 0 to JSONResult.Count -1)
.. (then retrieveing not only the Values via .ItemByIndex(i), but also the Keys via .KeyByIndex(i)
If we look at the concrete JSON you've posted (for one of your recent results):
{"result":{"require_confirm":false,"bookings":[{"id":"16","event_id":1,"unit_id":1,"client_id":4,"start_date_time":"2018-02-23 15:15:00","end_date_time":"2018-02-23 15:30:00","time_offset":"0","is_confirmed":"1","require_payment":false,"code":"sd8fggn","hash":"2ade 3784471cb9509d22d85ffbc60682"}]},"id":"5","jsonrpc":"2.0"}
... then you can make use of the returned RC5-JSONObject, to "reach into the hierarchy" above directly (without writing your own parsing), doing this:
Code:
Dim IsConfirmed As Boolean
IsConfirmed = JSONResult("bookings")(0)("is_confirmed")
The above being just the VB6-Notation of "accessing a Value over a JSON-Path within the returned JSON-hierarchy".
The "Path" in question (to reach the boolean value above) being: "bookings/0/is_confirmed".
You could write your own "Value-Retrieval-Function that accepts JSON-Path-arguments as a single StringPath-parameter",
or stick to the Parentheses, as I've shown them above in the small code-snippets.
As for "proper visualization" (to give you an overview, what such "Paths into the Hierarchy might be")...
In a previous example above I've used the MSHFlexGrid to visualize "only a certain, quite "flat" result (to show the "EventList").
But the FlexGrid is not sufficient to visualize "all kinds of JSON-responses" in a truly generic manner (without investing larger coding-efforts).
If you want to have a "generic JSON-Visualization", which (in a StatusBar) also gives you an Info,
what a certain JSON-Path might be (to reach a certain value):
- you could register the vbWidgets.dll (which comes within the RC5-package as well)
- place a reference to vbWidgets in your Project (in addition to the vbRichClient5 one)
And then include a single additional Class into your Project (which wraps an RC5-WidgetForm)
e.g. name it: cfJSONViewer - and then place the following content in it:
Code:
Option Explicit 'this JSON-Viewer-class requires a reference to a (previously registered) vbWidgets.dll
Public WithEvents Form As cWidgetForm, WithEvents jsTree As cwTree, Status As cwStatusBar
Public Sub ShowJSONData(oJSON As cCollection)
InitFormAndLoadWidgets
Dim DS As cDataSource 'prepare a JSON-DataSource for later Binding to the jsTree-Widget
Set DS = New_c.DataSource 'instantiate a new DataSource
DS.Init oJSON, "jsTreeSrc", , True 'initialize the DataSource with the passed JSON-Data
DS.TreeNodeExpand oJSON, True 'set all Node-Objects inside the DS to "expanded state"
Set jsTree.DataSource = DS 'apply the DataSource to the Widget which will visualize it
jsTree.DataSource.MovePrevious 'ensure "no current selection" on the TreeView
Form.Show vbModal 'show the RC5-Form (which hosts the jsTree-Widget) modally
End Sub
Private Sub InitFormAndLoadWidgets()
Set Form = Cairo.WidgetForms.Create(vbSizable, "JSON-Viewer", , 800, 600)
Form.SetMinMaxDimensions 640, 480 'let's just restrict the minimal Form-Dimensions to 640x480 Pixel
Set jsTree = Form.Widgets.Add(New cwTree, "jsTree", 0, 0, 350, 100) 'create the JSON-Tree-Widget
jsTree.Caption = "Simple 2-Column-TreeList, filled directly from a JSON-String"
Set Status = Form.Widgets.Add(New cwStatusBar, "Status", , , 1, 35)
End Sub
Private Sub Form_ResizeWithDimensions(ByVal NewWidth As Long, ByVal NewHeight As Long)
jsTree.Widget.Move 0, 0, NewWidth, NewHeight - Status.Widget.Height
End Sub
Private Sub jsTree_Click()
Dim FullPath As String, Value, Key, ParentNode As cCollection
If Not jsTree.DataSource.TreeElementInfoByVisibleIndex(jsTree.ListIndex, Key, Value, , , ParentNode) Then Exit Sub
FullPath = jsTree.DataSource.TreeNodeFullPath(IIf(IsObject(Value), Value, ParentNode), "/")
If IsEmpty(Value) Then Value = "null"
If Not IsObject(Value) Then FullPath = FullPath & IIf(Len(FullPath), "/", "") & Key & IIf(Len(Value), vbCrLf & "Value: " & Value, "")
Status.Caption = "Path: " & FullPath
End Sub
Private Sub jsTree_OwnerDrawItem(ByVal Index As Long, CC As vbRichClient5.cCairoContext, ByVal dx As Single, ByVal dy As Single, ByVal Alpha As Single)
'define your Base-Offsets for indentation in the consts below
Const BaseOffsX As Long = 7, Indent As Long = 12, ArrowSize As Long = 8, IconSize As Long = 0
'all the Vars defined below, will get filled in the call that follows (ByRef)
Dim Key, Value, Expanded As Boolean, Level As Long '<-
If Not jsTree.DataSource.TreeElementInfoByVisibleIndex(Index, Key, Value, Expanded, Level) Then Exit Sub
'Ok ... drawing-time - first calculate the current (level-dependent) X-Offset
Dim x As Long: x = BaseOffsX + (Level + 1) * Indent
'ensure a different Background-Line-Coloring for Odd-Indexes (in BorderColor with a low Alpha-Value of 4%)
If Index Mod 2 Then CC.Paint 1, Cairo.CreateSolidPatternLng(jsTree.Widget.BorderColor, 0.04)
'in the left Column we draw the Keys
CC.DrawText x, 0, dx \ 2 - x, dy, CStr(Key), True, vbLeftJustify, 3, True
If IsObject(Value) Then 'we have a Node-Entry
jsTree.DrawArrow CC, Index, x - IconSize - ArrowSize, ArrowSize, Expanded 'any Node gets a small Triangle drawn, using a Helper-Function in cwTree
Value = IIf(Value.IsJSONArray, "JSON-Array", "JSON-Object") 'convert the Value of Type cCollection to a red-colored String-Entry
jsTree.Widget.SelectFontSettingsInto CC, vbRed 'override the Default-TextColor to Red
End If
'in the right Column we draw the Values
If IsEmpty(Value) Then Value = "null"
CC.DrawText dx \ 2, 0, dx \ 2, dy, CStr(Value), True, vbLeftJustify, 4, True
jsTree.Widget.SelectFontSettingsInto CC 'reset to the Default-TextColor again (in case we rendered a Red-colored Node-Description)
'finally we draw a simple grey line (with 18% Alpha), to separate the left- from the right-column
CC.DrawLine dx \ 2, 0, dx \ 2, dy, True, 1, jsTree.Widget.BorderColor, 0.18
End Sub
Usage of the above "Visualizer-Class" would then require placing the following Line in a *.bas Module:
Code:
Public fJSONViewer As New cfJSONViewer
After that, you can at any time check your returned JSON-Results (in your Forms or anywhere else) by using a line like shown below:
Code:
fJSONViewer.ShowJSONData SBook.getEventList(True, True, OnlyClasses)
Or directly visualizing the "usual SubNode" of the returned JSON (as it e.g. sits behind the "result"-property, which seems quite common and "reliably there" in that API):
Code:
fJSONViewer.ShowJSONData SBook.getEventList(True, True, OnlyClasses).Prop("result")
Then producing output like that one here:
HTH
Olaf
-
Feb 25th, 2018, 09:07 AM
#25
Re: [RESOLVED] Json
Great learning materials.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|