Results 1 to 25 of 25

Thread: [RESOLVED] Json

  1. #1

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    Resolved [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&param2=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.

  2. #2
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    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.

  3. #3

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    Re: Json

    Quote Originally Posted by dilettante View Post
    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.

  4. #4
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: Json

    Quote Originally Posted by Darkbob View Post
    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

  5. #5

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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.

  6. #6

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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?

  7. #7
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: Json

    Quote Originally Posted by Darkbob View Post
    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

    Quote Originally Posted by Darkbob View Post
    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

  8. #8

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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.

  9. #9

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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.

  10. #10
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: [RESOLVED] Json

    Quote Originally Posted by Darkbob View Post
    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

  11. #11

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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.

  12. #12
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: [RESOLVED] Json

    Quote Originally Posted by Darkbob View Post
    {"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

  13. #13

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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.

  14. #14

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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.

  15. #15
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: [RESOLVED] Json

    Quote Originally Posted by Darkbob View Post
    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


  16. #16

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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?

  17. #17
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: [RESOLVED] Json

    Quote Originally Posted by Darkbob View Post
    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

  18. #18

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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?

  19. #19
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: [RESOLVED] Json

    Quote Originally Posted by Darkbob View Post
    OMG I frickin GOT IT WORKING!...
    Glad you did (on your own this time)...

    Quote Originally Posted by Darkbob View Post
    I can't imagine people are born knowing this stuff.
    Of course not...

    Quote Originally Posted by Darkbob View Post
    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

  20. #20

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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???

  21. #21
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: [RESOLVED] Json

    Quote Originally Posted by Darkbob View Post
    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

  22. #22

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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.

  23. #23

    Thread Starter
    Fanatic Member
    Join Date
    Oct 2005
    Posts
    564

    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.

  24. #24
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,255

    Re: [RESOLVED] Json

    Quote Originally Posted by Darkbob View Post
    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

  25. #25
    PowerPoster
    Join Date
    Sep 2012
    Posts
    2,083

    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
  •  



Click Here to Expand Forum to Full Width