VBA-tools / VBA-Web

VBA-Web: Connect VBA, Excel, Access, and Office for Windows and Mac to web services and the web
http://vba-tools.github.io/VBA-Web/
MIT License
2.01k stars 494 forks source link

JSON Parsing Error with Clickatell API #380

Closed matmasr closed 5 years ago

matmasr commented 5 years ago

I am trying to implement a simple API call to Clickatell REST service to send SMS messages to a group of phone numbers. I tried multiple ways but this is the code that seems to me the closest to what I need but still with errors. Public Sub SendClickatell() Dim ClickUrl As String ClickUrl = "https://platform.clickatell.com/messages" Dim AlertMessage As String AlertMessage = "This is a Test" Dim Client As New WebClient Client.BaseUrl = ClickUrl Dim Request As New WebRequest Request.Method = WebMethod.HttpPost Request.AddHeader "Content-Type", "application/json" Request.AddHeader "Accept", "application/json" Request.AddHeader "Authorization", "mysecerttoken" Request.Format = WebFormat.FormUrlEncoded Request.ResponseFormat = WebFormat.Json Dim SmsPhones As New Collection SmsPhones.Add Array("123456789", "1567898793", "52021250215") Dim SmsBody As New Dictionary SmsBody.Add "content", "Hello World" SmsBody.Add "to", SmsPhones Request.Body = ConvertToJson(SmsBody) Dim Response As WebResponse Set Response = Client.Execute(Request) Debug.Print Response.Content If Not Response.Data Is Nothing Then Debug.Print ConvertToJson(Response.Data) End If End Sub

This is how the REST API should be formatted based on Clickatell API

curl -i \ -X POST \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -H "Authorization: mysecerettoken" \ -d '{"content": "Hello world this is a message", "to": ["123456789"]}' \ -s https://platform.clickatell.com/messages ` The error I get seems to be that I am sending something is going to the server but in the wrong format. Here is the error

{"messages":[],"errorCode":602,"error":"Empty message content.","errorDescription":"Provide some content to be sent."} {"messages":[],"errorCode":602,"error":"Empty message content.","errorDescription":"Provide some content to be sent."}

zgrose commented 5 years ago

Your request is not a FormUrlEncoded. Note the lack of a form field in the example: -d '{"content": "Hello world this is a message", "to": ["123456789"]}'

Forms POST like: field1=value&field2=value You either want WebFormat.PlainText or WebFormat.Json for your Request.Format. I don't use the client bits enough to know which will dump the string into the body off-hand.

matmasr commented 5 years ago

Thanks for the comment and your help. When I used Webformat.Json I get so many parsing issues. I will change a send back results. Question though, am I formatting the Phone number(s) key and value correctly ?

matmasr commented 5 years ago

So I tested the same code but with Webformat.PlainText and got the following error

{"error":"HTTP 415 Unsupported Media Type"} {"error":"HTTP 415 Unsupported Media Type"}

matmasr commented 5 years ago

So with the Webformat.Json and having to change Request.Body = ConvertToJson(SmsBody) to just Set Request.Body = SmsBody I get the following error

ERROR - WebHelpers.ParseByFormat: 11000, An error occurred during parsing 10001: Error parsing JSON: Can not des ^ Expecting '{' or '[' ERROR - WebHelpers.ParseByFormat: 11000, An error occurred during parsing 10001: Error parsing JSON: Can not des ^ Expecting '{' or '[' Can not deserialize instance of java.lang.String out of START_ARRAY token at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream@49b5e7fc; line: 1, column: 32] (through reference chain: com.clickatell.mc2.rs.request.SendMessageRequest["to"]->java.util.HashSet[0])

which was my original error

zgrose commented 5 years ago

Try Request.Body = SmsBody because the client should be doing all the encoding for you based on your Format.

matmasr commented 5 years ago

Thanks again .. the errors I got above are with me setting Request.Body = SmsBody having to use Set Request.Body = SmsBody to avoid a Run Time error 449 "Argument not optional". I agree with you that the client should be doing all the encoding but then I have that 11000 error

zgrose commented 5 years ago

"That 11000 error" is because the server is not returning a JSON object, it is returning an error message:

Can not deserialize instance of java.lang.String out of START_ARRAY token
at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream@49b5e7fc; line: 1, column: 32] (through reference chain: com.clickatell.mc2.rs.request.SendMessageRequest["to"]->java.util.HashSet[0])

You can set your response to PlainText (I think) so you can just deal with the raw HTTP bodies while troubleshooting.

matmasr commented 5 years ago

Hey that worked ! At least for understanding the error which is now down to

{"error":"HTTP 406 Not Acceptable"} .. so something is not being encoded correctly. Is there something in my code that is obviously wrong ?

timhall commented 5 years ago

To log what is being sent, try WebHelpers.EnableLogging = True before the request is sent and viewing the logged request in the Immediate Window (ctrl+g or View > Immediate Window). Also, you can view just the formatted request body by setting a breakpoint before the request is sent and running Debug.Print Request.Body or ? Request.Body in the Immediate Window to view the body value that will be sent.

zgrose commented 5 years ago

Apparently the API is smart enough to know that you're asking for PlainText and it doesn't want to give it to you:

The HyperText Transfer Protocol (HTTP) 406 Not Acceptable client error response code indicates that the server cannot produce a response matching the list of acceptable values defined in the request's proactive content negotiation headers, and that the server is unwilling to supply a default representation.

So you'll have to stick with Request.ResponseFormat = WebFormat.Json and then write them a nasty letter to support that they aren't returning JSON error messages. :)

Your best bet probably seems to be to set a breakpoint in WebClient class, Execute method on the web_Http.send Request.Body line and make sure it's writing what you think it should be writing. Using a tool like PostMan can also cut down on all the curl transcribing from command line to code.

matmasr commented 5 years ago

Thank you guys so much for your help ... I will try these and see where the gap is ...

matmasr commented 5 years ago

So I turned on the debugging and saw the .Body result and the server response below. The only discrepancy I can see in the parsed JSON code is the double [[ instead of just the single [ where the phone numbers Array. Could this be the reason why the server is rejecting the submission and if so how would I apply the Array to the Body ?

--> Request - 3:02:24 PM POST https://platform.clickatell.com/messages Content-Type: application/json Accept: text/plain Authorization: mysecretttoken I replaced this but it was encoded correctly User-Agent: VBA-Web v4.1.5 (https://github.com/VBA-tools/VBA-Web) Content-Length: 79

{"content":"This is a Test","to":[["123456789","1324567892","13087458852"]]}

<-- Response - 3:02:25 PM 500 Internal Server Error Connection: keep-alive Date: Wed, 19 Dec 2018 21:02:27 GMT Content-Length: 35 Server: nginx Strict-Transport-Security: max-age=31536000; includeSubDomains

{"error":"HTTP 406 Not Acceptable"}

{"error":"HTTP 406 Not Acceptable"}

{"error":"HTTP 406 Not Acceptable"}

{"error":"HTTP 406 Not Acceptable"}

matmasr commented 5 years ago

This is the result withRequest.ResponseFormat = WebFormat.Json which seems to indicate that the double [[ maybe the culprit

--> Request - 3:13:45 PM POST https://platform.clickatell.com/messages Content-Type: application/json Accept: application/json Authorization: mysecrettoken User-Agent: VBA-Web v4.1.5 (https://github.com/VBA-tools/VBA-Web) Content-Length: 79

{"content":"This is a Test","to":[["123456789","1324567892","13087458852"]]}

ERROR - WebHelpers.ParseByFormat: 11000, An error occurred during parsing 10001: Error parsing JSON: Can not des ^ Expecting '{' or '[' ERROR - WebHelpers.ParseByFormat: 11000, An error occurred during parsing 10001: Error parsing JSON: Can not des ^ Expecting '{' or '[' <-- Response - 3:13:46 PM 400 Bad Request Connection: keep-alive Date: Wed, 19 Dec 2018 21:13:47 GMT Content-Length: 308 Content-Type: text/plain Server: nginx Strict-Transport-Security: max-age=31536000; includeSubDomains

Can not deserialize instance of java.lang.String out of START_ARRAY token at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream@52955f9f; line: 1, column: 35] (through reference chain: com.clickatell.mc2.rs.request.SendMessageRequest["to"]->java.util.HashSet[0])

Can not deserialize instance of java.lang.String out of START_ARRAY token at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream@52955f9f; line: 1,

zgrose commented 5 years ago

to":[["123456789","1324567892","13087458852"]]}

This isn't correct.

instead of:

Dim SmsPhones As New Collection
SmsPhones.Add Array("123456789", "1567898793", "52021250215")
...
SmsBody.Add "to", SmsPhones

do this

Dim SmsPhones As New Collection
SmsPhones.Add "123456789"
SmsPhones.Add "1567898793"
SmsPhones.Add "52021250215"
...
SmsBody.Add "to", SmsPhones

You were creating a 2D array.

matmasr commented 5 years ago

That was it ! I got the first text messages sent from excel to multiple numbers with one click. knew something simple was causing this .. turns out to be my VBA skills : ) ... Thank you so much for your help ... at least now I know I can parse and communicate with the server ... Both of you are awesome !