-
Jun 19th, 2017, 03:44 PM
#41
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
I think you read the API docs correctly the first time. There is a "/search/series" what is what we have been doing, and a "/series/{id}" which is the API you are looking at now. I added both Classes to the program just because it will also be needed later (If I can get the search series to work). I now have:
Code:
Public Class SearchSeriesResult
Public Property aliases() As String
Public Property banner As String
Public Property firstAired As String
Public Property id As Long
Public Property network As String
Public Property overview As String
Public Property seriesName As String
Public Property status As String
End Class
Public Class SeriesResults
Public Property data() As SeriesResult
Public Property errors As SeriesError
End Class
Public Class SeriesResult
Public Property added As String
Public Property airsDayOfWeek As String
Public Property airsTime As String
Public Property aliases() As String
Public Property banner As String
Public Property firstAired As String
Public Property genre() As String
Public Property id As Long
Public Property imbId As String
Public Property lastUpdated As Long
Public Property network As String
Public Property netwirkId As String
Public Property overview As String
Public Property rating As String
Public Property runtime As String
Public Property seriesID As Long
Public Property seriesName As String
Public Property siteRating As Long
Public Property siteRatingCount As Long
Public Property zap2itId As String
End Class
Public Class SeriesError
Public Property invalidFilters() As String
Public Property invalidLanguage As String
Public Property invalidQueryParams() As String
End Class
Jim
Originally Posted by Sitten Spynne
OK. Either I COMPLETELY read the original JSON wrong, or there's been an API update and the format has changed. What you got back looked really different than what I remembered, so I just double checked. You are getting correct JSON based on today's documentation. So let's start over. We have to rethink how we do a "SearchBySeries" request.
(APPARENTLY I picked entirely the wrong API here, but the answer is still really similar so I'm editing it to something that makes more sense!)
-
Jun 19th, 2017, 04:08 PM
#42
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
These are the classes I have now: (I added the SeriesResults for the next step (After the SearchSeriesResult works).
I left a message earlier about the "/search/series" being the one we were working on, but it didn't post for some reason.
Code:
Public Class SearchSeriesResult
Public Property aliases() As String
Public Property banner As String
Public Property firstAired As String
Public Property id As Long
Public Property network As String
Public Property overview As String
Public Property seriesName As String
Public Property status As String
End Class
Public Class SeriesResults
Public Property data() As SeriesResult
Public Property errors As SeriesError
End Class
Public Class SeriesResult
Public Property added As String
Public Property airsDayOfWeek As String
Public Property airsTime As String
Public Property aliases() As String
Public Property banner As String
Public Property firstAired As String
Public Property genre() As String
Public Property id As Long
Public Property imbId As String
Public Property lastUpdated As Long
Public Property network As String
Public Property netwirkId As String
Public Property overview As String
Public Property rating As String
Public Property runtime As String
Public Property seriesID As Long
Public Property seriesName As String
Public Property siteRating As Long
Public Property siteRatingCount As Long
Public Property zap2itId As String
End Class
Public Class SeriesError
Public Property invalidFilters() As String
Public Property invalidLanguage As String
Public Property invalidQueryParams() As String
End Class
Originally Posted by Sitten Spynne
Huh, OK. I'm going back and editing because it's still sort of almost right, the only difference is the inner type now will have different properties. I'm going fix it!
Done. Thanks for pointing it out, and reminding me that there's 2 different places VB might want you to put parenthesis on array brackets, and four different contexts, and not all placements are all valid in all contexts.
It's simple scenarios like that that make VB so easy to learn compared to C#, what with its one place for array brackets. Who has time to memorize one rule when you could be memorizing at least four?
-
Jun 19th, 2017, 04:13 PM
#43
Re: Help using JSON API that requires Authentication with JWT token
Yeah, you got a good read on it. I'm curious to see how the new stuff works.
Note that '/series/{id}' is a new pattern for a REST call. You can sort of cheat it into the HTTP layer without adding a new call.
For this kind of API endpoint, one of the variables is part of the URL, but not part of query parameters. I'm not sure exactly what an 'id' is in this context, maybe a number. So if you wanted stuff about the series with id "1234", the URL would be '/series/1234', plus any query parameters if needed.
So you don't really need to add a new GET or POST method, just do a little magic on the URL before calling it. You could instead choose to do the URL magic inside the HTTP layer, but that'd require a new call. The "new call" way is probably the most architecturally pure, but occasionally I feel like "being pure" involves more work than it's worth.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Jun 19th, 2017, 09:14 PM
#44
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
I'm still not able to deserialize the search/series Json results. I am getting the error:
Newtonsoft.Json.JsonReaderException was unhandled
HResult=-2146233088
LineNumber=4
LinePosition=18
Message=Unexpected character encountered while parsing value: [. Path 'data[0].aliases', line 4, position 18.
Path=data[0].aliases
When I execute the following code"
Code:
Dim responseObject = JsonConvert.DeserializeObject(Of SearchSeriesResults)(searchResults)
The JSON Text looks like this:
Code:
{
"data": [
{
"aliases": [
"Big Bang"
],
"banner": "graphical/80379-g15.jpg",
"firstAired": "2007-09-24",
"id": 80379,
"network": "CBS",
"overview": "What happens when hyperintelligent roommates Sheldon and Leonard meet Penny, a free-spirited beauty moving in next door, and realize they know next to nothing about life outside of the lab. Rounding out the crew are the smarmy Wolowitz, who thinks he's as sexy as he is brainy, and Koothrappali, who suffers from an inability to speak in the presence of a woman.",
"seriesName": "The Big Bang Theory",
"status": "Continuing"
},
{
"aliases": [],
"banner": "graphical/290555-g2.jpg",
"firstAired": "2007-10-02",
"id": 290555,
"network": "Mega Channel",
"overview": null,
"seriesName": "Big Bang (Mega)",
"status": "Ended"
},
I am wondering if it is having trouble with the extra white space in the brackets of aliases:
Jim
Originally Posted by Sitten Spynne
Huh, OK. I'm going back and editing because it's still sort of almost right, the only difference is the inner type now will have different properties. I'm going fix it!
Done. Thanks for pointing it out, and reminding me that there's 2 different places VB might want you to put parenthesis on array brackets, and four different contexts, and not all placements are all valid in all contexts.
It's simple scenarios like that that make VB so easy to learn compared to C#, what with its one place for array brackets. Who has time to memorize one rule when you could be memorizing at least four?
-
Jun 19th, 2017, 10:02 PM
#45
Re: Help using JSON API that requires Authentication with JWT token
I don't see a 'SearchSeriesResults' type in the code you've posted. I see 'SeriesResults', "SeriesResult", and "SearchSeriesResult". But I'm missing exactly the type I need to see to think about how the parser might be interpreting the data.
At the same time, pay attention to Inferred. VB has some ridiculous, stupid rules about parenthesis because it made the ridiculous, stupid decision to use them for array indices.
If you were using a "hard" language like C#, there would be one rule to remember: "Array of Something is always Something[]". But since VB is an "easy" language, you have to remember:
- For fields/local variables, there are two valid syntaxes for declaring an array variable. "Public x() As Something", "Public x As Something()".
- But only one is legal if you want to declare bounds: "Public x(3) As Something".
- Properties need parenthesis by their names, so their array type indicator needs to be near the type: "Public Property X() As Something()".
- It's illegal to declare array bounds for a property.
So, since I was typing code by memory, I forgot that the property should be:
Code:
Public Property aliases() As String()
Welcome to VB, where the only rule with no exceptions is "memorize the language reference".
(There's a technical reason, but not a good one. There is a concept in .NET called 'indexers' that maps to something VB6 called "the default property". In C# the rule is "you have to give it a specific name that is invalid in any other context, and it's not legal to call it like a method". In VB the rule is "lol you can call it whatever you want, and even call it directly if you want, so since any property MIGHT be an indexer, EVERY property needs extra syntax. Just in case."
Like I said, in VB if you MIGHT have to type something, you generally HAVE to. Even if it's obvious you don't need it.)
Last edited by Sitten Spynne; Jun 19th, 2017 at 10:09 PM.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Jun 19th, 2017, 10:26 PM
#46
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
Sorry, I guess I missed that one.
Code:
Public Class SearchSeriesResults
Public Property data() As SearchSeriesResult()
End Class
Public Class SearchSeriesResult
Public Property aliases() As String
Public Property banner As String
Public Property firstAired As String
Public Property id As Long
Public Property network As String
Public Property overview As String
Public Property seriesName As String
Public Property status As String
End Class
Originally Posted by Sitten Spynne
I don't see a 'SearchSeriesResults' type in the code you've posted. I see 'SeriesResults', "SeriesResult", and "SearchSeriesResult". But I'm missing exactly the type I need to see to think about how the parser might be interpreting the data.
At the same time, pay attention to Inferred. VB has some ridiculous, stupid rules about parenthesis because it made the ridiculous, stupid decision to use them for array indices.
If you were using a "hard" language like C#, there would be one rule to remember: "Array of Something is always Something[]". But since VB is an "easy" language, you have to remember:
- For fields/local variables, there are two valid syntaxes for declaring an array variable. "Public x() As Something", "Public x As Something()".
- But only one is legal if you want to declare bounds: "Public x(3) As Something".
- Properties need parenthesis by their names, so their array type indicator needs to be near the type: "Public Property X() As Something()".
- It's illegal to declare array bounds for a property.
So, since I was typing code by memory, I forgot that the property should be:
Code:
Public Property aliases() As String()
Welcome to VB, where the only rule with no exceptions is "memorize the language reference".
(There's a technical reason, but not a good one. There is a concept in .NET called 'indexers' that maps to something VB6 called "the default property". In C# the rule is "you have to give it a specific name that is invalid in any other context, and it's not legal to call it like a method". In VB the rule is "lol you can call it whatever you want, and even call it directly if you want, so since any property MIGHT be an indexer, EVERY property needs extra syntax. Just in case."
Like I said, in VB if you MIGHT have to type something, you generally HAVE to. Even if it's obvious you don't need it.)
-
Jun 20th, 2017, 07:24 AM
#47
Re: Help using JSON API that requires Authentication with JWT token
Update the aliases property in SearchSeriesResult too!
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Jun 20th, 2017, 07:32 AM
#48
Re: Help using JSON API that requires Authentication with JWT token
Originally Posted by JimAvanti
Sorry, I guess I missed that one.
Jim,
You might have also missed how you were supposed to alter your Class definitions amidst Sitten's daily anti VB rant....
The syntax for declaring a Property as an array requires that the parentheses are placed after the name of the Type, not after the name of the Property.
So lines like:
Code:
Public Property aliases() As String
Public Property genre() As String
Public Property invalidFilters() As String
should be:
Code:
Public Property aliases As String()
Public Property genre As String()
Public Property invalidFilters As String()
There are also a couple of typos in the names you are using for the Properties:
'imbId' should be 'imdbId', and 'netwirkId' should be 'networkId'.
And the documentation for the API seems to be a little off.
For the /series/{id} endpoint, the object named "seriesId" seems to be returning a String, not a Long as documented.
They have also added an 'addedBy' object.
With this in mind, the following is my take on how you may want to define your Classes. I have highlighted the changes from your original code:
Code:
Public Class SearchSeriesResults
Public Property data As SearchSeriesResult()
End Class
Public Class SearchSeriesResult
Public Property aliases As String()
Public Property banner As String
Public Property firstAired As String
Public Property id As Long
Public Property network As String
Public Property overview As String
Public Property seriesName As String
Public Property status As String
End Class
Code:
Public Class SeriesResults
Public Property data As SeriesResult
Public Property errors As SeriesError
End Class
Public Class SeriesResult
Public Property added As String
Public Property addedBy As Nullable(Of Long)
Public Property airsDayOfWeek As String
Public Property airsTime As String
Public Property aliases As String()
Public Property banner As String
Public Property firstAired As String
Public Property genre As String()
Public Property id As Long
Public Property imdbId As String
Public Property lastUpdated As Long
Public Property network As String
Public Property networkId As String
Public Property overview As String
Public Property rating As String
Public Property runtime As String
Public Property seriesID As String ' documentation has this as Long
Public Property seriesName As String
Public Property siteRating As Long
Public Property siteRatingCount As Long
Public Property status As String
Public Property zap2itId As String
End Class
Public Class SeriesError
Public Property invalidFilters As String()
Public Property invalidLanguage As String
Public Property invalidQueryParams As String()
End Class
-
Jun 20th, 2017, 08:20 AM
#49
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
That did it. I am able to get the specific items now.
Code:
Dim responseObject = JsonConvert.DeserializeObject(Of SearchSeriesResults)(searchResults)
TextBox1.Text = TextBox1.Text & vbCrLf & responseObject.data(0).id
Thanks,
Jim
Originally Posted by Inferrd
Jim,
You might have also missed how you were supposed to alter your Class definitions amidst Sitten's daily anti VB rant....
The syntax for declaring a Property as an array requires that the parentheses are placed after the name of the Type, not after the name of the Property.
So lines like:
Code:
Public Property aliases() As String
Public Property genre() As String
Public Property invalidFilters() As String
should be:
Code:
Public Property aliases As String()
Public Property genre As String()
Public Property invalidFilters As String()
[/CODE]
-
Jun 20th, 2017, 09:50 AM
#50
Re: Help using JSON API that requires Authentication with JWT token
Whew! Thanks for picking up all the extra ones, Inferrd.
JimAvanti: get used to carefully comparing the names of your properties and the types of your properties to what you're seeing in the JSON and what you're seeing in the documentation. It doesn't seem like these people are very disciplined about keeping their API and documentation in sync. The first 90% of working with that kind of API is writing something that matches the documentation. The second 90% is figuring out what they ACTUALLY send you.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Jun 20th, 2017, 10:45 AM
#51
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
Going back to the working GetWithJwt function, I am trying to catch errors. I would like to read the StatusCode response from the WebRequest. There are some codes I would like to check on (401 if unauthorized, 404 if the search string is not found,...). If an error code happens on
Code:
Using response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
then it never gets to check the statusCode. If I use "Try/Catch" then I can't check the StatusCode in the catch because it is only available in the Using. Is there a way of checking the resonse.stausCode if there is an error with GetResopnse?
Jim
Code:
Public Function GetWithJwt(ByVal url As String, ByVal jwt As String, Optional ByVal parameters As Dictionary(Of String, String) = Nothing) As String
Dim encodedUrl As String = url
If parameters IsNot Nothing Then
encodedUrl = CreateEncodedParameterizedUrl(url, parameters)
End If
' Configure the request headers.
Dim request As HttpWebRequest = CType(HttpWebRequest.Create(encodedUrl), HttpWebRequest)
request.Method = "GET"
' Build the JWT string and add the right header.
Dim jwtHeaderValue As String = String.Format("Bearer {0}", jwt)
request.Headers.Add(HttpRequestHeader.Authorization, jwtHeaderValue)
' Read the response and its JSON payload.
Try
Using response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
If response.StatusCode <> HttpStatusCode.OK Then
Throw New ApplicationException("There was an error with the request.")
End If
Using responseStream As Stream = response.GetResponseStream()
Using reader As New StreamReader(responseStream, Text.Encoding.UTF8)
Return reader.ReadToEnd()
End Using
End Using
End Using
Catch ex As Exception
End Try
End Function
-
Jun 20th, 2017, 11:43 AM
#52
Re: Help using JSON API that requires Authentication with JWT token
I don't really like how HttpWebRequest does this, in a lot of cases a non-200 status code isn't really an "error" so making you go through exception handling is a bother. Nonetheless, you have the tools you need.
If you check the documentation for HttpWebRequest.GetResponse(), you'll find a list of exceptions it might throw. Handling all of them is important, but in this case you are writing code that needs to do something special for WebException, which is thrown when "an error occurred while processing the request".
If that exception is thrown, then the HttpWebResponse was not actually created for the Using statement! You could have rearranged things to get access to that variable, but you would have found it was Nothing. Generally, methods in .NET either do what they say OR throw an exception. Anyway, peek at the WebException documentation. Read all of its properties. The most important one is Response, which gives you access to the WebResponse that WOULD HAVE been created. This is almost the same thing as an HttpWebResponse, and is probably safe to cast, but I don't know yet if you need to.
So:
Code:
Try
Using response As HttpWebResponse = ...
...
End Using
Catch ex As WebException
Dim response As HttpWebException = CType(ex.Response, HttpWebException)
Dim statusCode As HttpStatusCode = e.StatusCode
If statusCode = HttpStatusCode.Unauthorized Then
...
End If
End Catch
A good question: Why didn't I put a Using statement in the Catch block? I have a hunch you aren't supposed to. This HttpWebResponse belongs to the WebException. It's that type's responsibility to dispose of it. If I dispose of it, I might cause trouble. The examples on MSDN don't dispose of the response when they get it. THAT SAID, it might be an oversight, and they might not have thought about it. So if you're having problems, add a Using statement to make sure it gets disposed.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Jun 20th, 2017, 12:21 PM
#53
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
I was surprised to see a status code other than 200 handled as an exception error also. That is why I thought there was something wrong and added the try/catch. When I pass a search string it couldn’t find I got the correct code 404, but it halted the program before I could even check the code.
I will need to have GetWithJwt pass any errors back to SearchSeriesByName which will have to pass the error back to the main calling command. The problem will be that SearchSeriesByName returns an SearchSeriesResults object (not a string) so I may have to serialize an errorcode into responseObject. Am I over thinking this or is there a much simpler way to go? I could also have SearchSeriesByName return a non-deserialized JSON string and deserialize it from the main program, that way I could simply send back the string “401”, “404” etc…
Jim
Originally Posted by Sitten Spynne
I don't really like how HttpWebRequest does this, in a lot of cases a non-200 status code isn't really an "error" so making you go through exception handling is a bother. Nonetheless, you have the tools you need.
-
Jun 20th, 2017, 04:22 PM
#54
Re: Help using JSON API that requires Authentication with JWT token
Well, there's plenty of viable approaches. Solving puzzles like that and having an error strategy is part of the fun.
You *could* choose to get what you want out of the WebException, then throw a more specific exception that holds just that data. Some people like this because they believe Exceptions are the only way you should handle errors. I don't like it, because Exceptions are generally for "unexpected" errors you're not sure how to recover from, and these are more predictable scenarios like "expired JWT".
You could add properties to SearchSeriesResults to represent the errors. The JSON parser doesn't really care if there are properties on the object that aren't in the JSON (unless you're serializing, then you have to tell it to ignore them.)
But what I like the best is to decide to add ANOTHER object to the hierarchy to represent "the results of making this request, which may be either a JSON string or some error information."
I sure do like wrapping objects with other objects, don't I? I do this because I don't want any one object to have to think too hard about the things "above" it. I don't want the types that are meant to represent JSON to have extra non-JSON stuff on them. That makes it likely I'll screw up and mix the two.
So I'd add an object:
Code:
Public Class ApiResponse
Public Property Result() As String
Public Property StatusCode() As Integer
End Class
So probably something like this:
Code:
Public Function GetWithJwt(...) As ApiResponse
...
Try
Using response As HttpWebResponse = ...
Using responseStream...
Using reader...
Dim responseData As String = reader.ReadToEnd()
Dim result As New ApiResponse()
result.StatusCode = 200
result.Result = result
Return result
End Using
End Using
End Using
Catch ex As WebException
Dim response As HttpWebException = CType(ex.Response, HttpWebException)
Dim responseData As String
Using reader As ....
responseData = reader.ReadToEnd()
End Using
Dim statusCode As HttpStatusCode = e.StatusCode
Dim result As New ApiResult()
result.Result = responseData
result.StatusCode = CInt(statusCode)
End Catch
That bubbles up the notion of a status code, and the layer above can decide exactly how to parse the JSON based on it.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Jun 21st, 2017, 10:23 AM
#55
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
I tried changing the GetWithJwt function so that if there was an error it would return it as a string and then the calling TvDbApi function could handle it by altering the Json text (id = 99999, and seriesName equal to the error code). That way it would pass back to any calling routine as valid JSON data, but building the JSON string was not easy with all the quotes involved so I gave up and just returned a Non-JSON string with the error.
Code:
Public Function GetWithJwt(ByVal url As String, ByVal jwt As String, Optional ByVal parameters As Dictionary(Of String, String) = Nothing) As String
Dim encodedUrl As String = url
If parameters IsNot Nothing Then
encodedUrl = CreateEncodedParameterizedUrl(url, parameters)
End If
' Configure the request headers.
Dim request As HttpWebRequest = CType(HttpWebRequest.Create(encodedUrl), HttpWebRequest)
request.Method = "GET"
' Build the JWT string and add the right header.
Dim jwtHeaderValue As String = String.Format("Bearer {0}", jwt)
request.Headers.Add(HttpRequestHeader.Authorization, jwtHeaderValue)
Try
Dim response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
Using responseStream As Stream = response.GetResponseStream()
Using reader As New StreamReader(responseStream, Text.Encoding.UTF8)
Return reader.ReadToEnd()
End Using
End Using
response.Close()
'Catch ex As WebException
' Return "Error " & ex.Message
Catch ex As Exception
Return "Error " & ex.Message
End Try
End Function
End Class
The calling routine was also changed to return a string instead of a JSON object:
Code:
Public Function SearchSeriesByName(ByVal jwt As String, ByVal searchTerm As String) As String
Dim restClient As New RestClient()
Dim url As String = "https://api.thetvdb.com/search/series"
Dim parameters As New Dictionary(Of String, String)()
parameters.Add("name", searchTerm)
Dim responseJson As String = restClient.GetWithJwt(url, jwt, parameters)
Return responseJson
End Function
I now deserialize in the main program:
Code:
Private Sub GetShowID()
ListView_Results.Items.Clear()
Dim tvDbApi As New TvDbApi()
Dim searchResults As String = tvDbApi.SearchSeriesByName(jwt, TextBox_Name.Text)
If searchResults.Substring(0, 5) <> "Error" Then
TextBox_Error.Clear()
Dim responseObject = JsonConvert.DeserializeObject(Of SearchSeriesResults)(searchResults)
Dim counter As Integer = 0
For Each row In responseObject.data
ListView_Results.Items.Add(responseObject.data(counter).seriesName)
ListView_Results.Items(counter).SubItems.Add(responseObject.data(counter).id.ToString)
counter = counter + 1
Next
Else
TextBox_Error.Text = searchResults
End If
End Sub
I like your way of doing it better and I may change it, but I wanted to move on to the next step (which is working!):
Code:
Public Function SearchSeriesById(ByVal jwt As String, ByVal searchTerm As String) As String
Dim restClient As New RestClient()
Dim url As String = "https://api.thetvdb.com/series/" & searchTerm
'Form1.TextBox_Token.Text = url
Dim responseJson As String = restClient.GetWithJwt(url, jwt)
Return responseJson
End Function
Code:
Private Sub ListView_Results_MouseDoubleClick(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles ListView_Results.MouseDoubleClick
TextBox_Info.Clear()
TextBox_Info.Text = ListView_Results.SelectedItems(0).Text
TextBox_Info.Text = TextBox_Info.Text & " " & ListView_Results.SelectedItems(0).SubItems(1).Text
Dim tvDbApi As New TvDbApi()
Dim searchResults As String = tvDbApi.SearchSeriesById(jwt, ListView_Results.SelectedItems(0).SubItems(1).Text)
If searchResults.Substring(0, 5) <> "Error" Then
Dim responseObject = JsonConvert.DeserializeObject(Of SeriesResults)(searchResults)
TextBox_Info.Text = TextBox_Info.Text & vbCrLf & responseObject.data.overview
TextBox_Info.Text = TextBox_Info.Text & vbCrLf & "First Aired: " & responseObject.data.firstAired
TextBox_Info.Text = TextBox_Info.Text & vbCrLf & "Network: " & responseObject.data.network
Else
TextBox_Error.Text = searchResults
End If
End Sub
I hear what you are saying about the API documentation not being trusted: The seriesID does need to be a string rather than a Long. I was getting errors on some shows that respond with “”.
I seem to be understanding just enough to get by for now.
Thanks for all your help,
Jim
Originally Posted by Sitten Spynne
Well, there's plenty of viable approaches. Solving puzzles like that and having an error strategy is part of the fun.
You *could* choose to get what you want out of the WebException, then throw a more specific exception that holds just that data. Some people like this because they believe Exceptions are the only way you should handle errors. I don't like it, because Exceptions are generally for "unexpected" errors you're not sure how to recover from, and these are more predictable scenarios like "expired JWT".
You could add properties to SearchSeriesResults to represent the errors. The JSON parser doesn't really care if there are properties on the object that aren't in the JSON (unless you're serializing, then you have to tell it to ignore them.)
But what I like the best is to decide to add ANOTHER object to the hierarchy to represent "the results of making this request, which may be either a JSON string or some error information."
I sure do like wrapping objects with other objects, don't I? I do this because I don't want any one object to have to think too hard about the things "above" it. I don't want the types that are meant to represent JSON to have extra non-JSON stuff on them. That makes it likely I'll screw up and mix the two.
-
Feb 19th, 2021, 09:25 PM
#56
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
Originally Posted by Sitten Spynne
Huh, OK. I'm going back and editing because it's still sort of almost right, the only difference is the inner type now will have different properties. I'm going fix it!
Done. Thanks for pointing it out, and reminding me that there's 2 different places VB might want you to put parenthesis on array brackets, and four different contexts, and not all placements are all valid in all contexts.
It's simple scenarios like that that make VB so easy to learn compared to C#, what with its one place for array brackets. Who has time to memorize one rule when you could be memorizing at least four?
Hello Sitten! You helped me out years ago when TheTVdb converted their API from XML to JSON. My program has been working great all these years, but they are now making major changes to the API again and I am running into problems again and was wondering if you are still around to help. This response is in the original message thread.
The format of the Login response changed and now I am getting a null JWT token. If I look at the responseJson string I can see the token, but when I try to deserialize to get the token I seems empty.
The old response string that worked is:
{"token":"hhjkbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTQzNzYzNjAsImlkIjoiVFZfQWlyRGF0ZSIsIm9ya WdfaWF0IjoxNjEzNzcxNTYwLCJ1c2VyaWQiOjk4NTM2LCJ1c2VybmFtZSI6IkppbUF2YW50aSJ9.HjputMMwKY4JeYft5Wqn99u1 H9mR90ooZkGxbhz4lfMoYUpPQpqQAG--LMl7VVlROHyszYHwAdSa1u1NHgJmjEMvE2Ijxfh8FOypgWbTEgKzdCATNn1NgSXgBYIIvKsF1JzkkH_0ieqdFTNOCr8fe2rOpV37 0g1lMl7MCKqn5OCs10u5TrDGnppQMltNPHHIyn4MsmQkKKhIgx9FnsCFYs117s5w5JvBW3Fm_9Q56tUzyfJtYEsOGp5--Bp6wlWEuEGkKscLf9A4ImIse45WRwmeL_EeGW8dtV9CTKEE1CnV4GRuTjDK2DISlbDTabkyBWD7x-T_KYVm9Ub7d_JBuA"}
The new format is:
{
"status": "success",
"data": {
"token": "ddffbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlrZXkiOiJlNmU4YzhmMy0zODA5LTRjZjQtODA0Zi1iZDUyMGE4MmZjOT IiLCJjb21tdW5pdHlfc3VwcG9ydGVkIjpmYWxzZSwiZXhwIjoxNjE0MzcwNTg3LCJpZCI6IjAiLCJwaW4iOiJzdHJpbmcifQ.Ukh AlKa6urHCWWJikxHeYRthKRbU-nCaOdRl9dwkIGAAZuwLRPmE2nSj563JaMmANCbqgVIUUm8zhdHVMSwC68pz-AVfOOQx8uiPifnZWbkUHTnSbM4q5b7EiVUUx1gKZW-tqkYK91LCeqegfFZG12KWcE551hL4B9COuuBIt8Q30IA_on4GrIdtPjd4no2v11nPH9-mZJHeomj0ljK2dmeqSmWvXfHq0VUFWO7N8LMk8V0UrEaJObHrK57CD7Zvai7jDkvAUxySBbZ71iYcIRdo7z5vnNKA2sBUuuNb3lN tiN6ioTpGKbfAPhs9Ayk1AKyIWUfthDREbwzjD1eosCFrxN0PP0B0fM45owvxnFoWrlUpWEAIpIdYhtIqFRK9vB5CkEXRF9yMhbZ ewhMHYS-G_Ga5bCG9NxUNKXqclamowxg6WPYqJvukM2mtJk1sWMGt8P19R8Wz5nFrlDBljB28AfxKdHE7mMMThZ4zOIH0vNot2Nk60pizndB JQlPxfnxqhkeeqBr3qXL5OJXkLDK5IQuhtiJQeE3IYIXTIMpzpUTQ9HeFq0dilPFXtpDm7W1S2FyvrVJdNkozRYGuFXZMPrcpeev a95sW__dbz2CgfTRZByLsu86xAsSquvDchtXiv-vjJR0gBWXeFjx2ctWYrnPY10twnRyOvhBOXCc"
}
}
This is the code being used. You can see the textBox I am displaying the results in. It seems that the property "token" is not being accessed correctly.
Code:
Public Class TvDbApi
Public Function Login(ByVal credentials As TvDbCredentials) As String
Dim restClient As New RestClient()
Dim url As String = "https://api4.thetvdb.com/v4/login"
Dim payloadJson As String = JsonConvert.SerializeObject(credentials)
Dim responseJson As String = restClient.Post(url, payloadJson)
Dim responseObject As LoginResult = JsonConvert.DeserializeObject(Of LoginResult)(responseJson)
'Form1.TextBox_Token.Text = responseJson
Form1.TextBox_Token.Text = responseObject.token
Return responseObject.token
End Function
-
Feb 19th, 2021, 09:56 PM
#57
Re: Help using JSON API that requires Authentication with JWT token
If you copy the JSON, open the Edit menu, Paste Special, and then select Paste JSON as Classes it will automatically generate a class representation of your JSON.
Personally, I would rename Rootobject to Response and also capitalize the property names but use the JsonProperty decorator to map the lowercase response properties.
This would leave you with a class representation of:
Code:
Public Class Response
<JsonProperty("status")>
Public Property Status As String
<JsonProperty("data")>
Public Property Data As Data
End Class
Public Class Data
<JsonProperty("token")>
Public Property Token As String
End Class
From here, you can still use the DeserializeObject method to get the token:
Code:
Public Function Login(ByVal credentials As TvDbCredentials) As String
Dim restClient = New RestClient()
Dim url = "https://api4.thetvdb.com/v4/login"
Dim payloadJson = JsonConvert.SerializeObject(credentials)
Dim responseJson = restClient.Post(url, payloadJson)
Dim responseObject = JsonConvert.DeserializeObject(Of Response)(responseJson)
Form1.TextBox_Token.Text = response.Data.Token
Return responseObject.Data.Token
End Function
-
Feb 19th, 2021, 10:23 PM
#58
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
DDay9,
Thanks! I don't quite understand what you did, but it worked! I'm going to try to figure out what you did because I'm sure I will need to make similar changes to the rest of the API calls. I also don't know what you mean by JsonProperty decorator or about pasting JSON as classes, but I will search that out later. I am using the vb.net 2010 so maybe those are features I don't have.
Thanks for your help. Now I can push a bit more forward.
Jim
Originally Posted by dday9
If you copy the JSON, open the Edit menu, Paste Special, and then select Paste JSON as Classes it will automatically generate a class representation of your JSON.
Personally, I would rename Rootobject to Response and also capitalize the property names but use the JsonProperty decorator to map the lowercase response properties.
This would leave you with a class representation of:
Code:
Public Class Response
<JsonProperty("status")>
Public Property Status As String
<JsonProperty("data")>
Public Property Data As Data
End Class
Public Class Data
<JsonProperty("token")>
Public Property Token As String
End Class
From here, you can still use the DeserializeObject method to get the token:
Code:
Public Function Login(ByVal credentials As TvDbCredentials) As String
Dim restClient = New RestClient()
Dim url = "https://api4.thetvdb.com/v4/login"
Dim payloadJson = JsonConvert.SerializeObject(credentials)
Dim responseJson = restClient.Post(url, payloadJson)
Dim responseObject = JsonConvert.DeserializeObject(Of Response)(responseJson)
Form1.TextBox_Token.Text = response.Data.Token
Return responseObject.Data.Token
End Function
-
Feb 20th, 2021, 11:11 AM
#59
Re: Help using JSON API that requires Authentication with JWT token
Ah, yeah it is likely like that the Paste JSON as Classes doesn't exist in 2010.
The JsonProperty decorator is the first line in this code:
Code:
<JsonProperty("status")>
Public Property Status As String
What it does when the DeserializeObject runs is say: I am looking for the "status" property in the JSON literal and I want to place the value in the "Status" property of the object.
I only did this because the naming convention that I follow requires property names to be uppercased.
-
Feb 20th, 2021, 11:30 AM
#60
Re: Help using JSON API that requires Authentication with JWT token
There is site which do the same job like paste as JSON: http://jsonutils.com/
Actually, the project itself is written in .NET so you can build it and run it locally in the intranet as the site itself is not using HTTPS.
-
Feb 20th, 2021, 12:01 PM
#61
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
Originally Posted by dday9
Ah, yeah it is likely like that the Paste JSON as Classes doesn't exist in 2010.
The JsonProperty decorator is the first line in this code:
Code:
<JsonProperty("status")>
Public Property Status As String
What it does when the DeserializeObject runs is say: I am looking for the "status" property in the JSON literal and I want to place the value in the "Status" property of the object.
I only did this because the naming convention that I follow requires property names to be uppercased.
Well, you pointed me in the right direction and with your comments I did some searching and found a website that automatically generates the JSON class from the API call results. https://jsonutils.com/
With that I was able to get the next API call (Series name search) working. I am on a roll!
I would never have figured out the class structure from the results I had on my own.
Thanks for your help,
Jim
-
Feb 22nd, 2021, 10:54 PM
#62
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
Originally Posted by peterst
There is site which do the same job like paste as JSON: http://jsonutils.com/
Actually, the project itself is written in .NET so you can build it and run it locally in the intranet as the site itself is not using HTTPS.
I just realized you told be about the jsonutil.com site after I searched and found it! I was able to do the next step which was an API call to search for a TV show and get the showID. I used jsonutil to convert the given Schema to a JSON class and it worked.
Now I need to do an API call to get the Show details from the ShowID. The Schema they give on the website does not seem to convert to a JSON Class correctly. Could you take a look at it? I saw there is an update for VS WebToold2012 which adds the "Paste as JSON Class", but it is not for VS2010.
This is the Schema they give:
Code:
{
"data": {
"abbreviation": {
"String": "string",
"Valid": true
},
"aliases": [
{
"language": "string",
"name": "string"
}
],
"country": {
"String": "string",
"Valid": true
},
"defaultSeasonType": 0,
"firstAired": {
"String": "string",
"Valid": true
},
"id": {
"Int64": 0,
"Valid": true
},
"image": {
"String": "string",
"Valid": true
},
"isOrderRandomized": true,
"lastAired": {
"String": "string",
"Valid": true
},
"name": {
"String": "string",
"Valid": true
},
"nameTranslations": [
"string"
],
"nextAired": "string",
"originalCountry": {
"String": "string",
"Valid": true
},
"originalLanguage": {
"String": "string",
"Valid": true
},
"overviewTranslations": [
"string"
],
"score": 0,
"slug": {
"String": "string",
"Valid": true
},
"status": {
"id": 0,
"keepUpdated": true,
"name": "string",
"recordType": "string"
}
},
"status": "string"
}
This is what jsonutils converts it to:
Code:
Public Class Abbreviation
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class Alias
Public Property language As String
Public Property name As String
End Class
Public Class Country
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class FirstAired
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class Id
Public Property Int64 As Integer
Public Property Valid As Boolean
End Class
Public Class Image
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class LastAired
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class Name
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class OriginalCountry
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class OriginalLanguage
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class Slug
Public Property String As String
Public Property Valid As Boolean
End Class
Public Class Status
Public Property id As Integer
Public Property keepUpdated As Boolean
Public Property name As String
Public Property recordType As String
End Class
Public Class Data
Public Property abbreviation As Abbreviation
Public Property aliases As Alias()
Public Property country As Country
Public Property defaultSeasonType As Integer
Public Property firstAired As FirstAired
Public Property id As Id
Public Property image As Image
Public Property isOrderRandomized As Boolean
Public Property lastAired As LastAired
Public Property name As Name
Public Property nameTranslations As String()
Public Property nextAired As String
Public Property originalCountry As OriginalCountry
Public Property originalLanguage As OriginalLanguage
Public Property overviewTranslations As String()
Public Property score As Integer
Public Property slug As Slug
Public Property status As Status
End Class
Public Class SearchBySeriesID
Public Property data As Data
Public Property status As String
End Class
It just doesn't look right to me.
Thanks,
Jim
-
Feb 23rd, 2021, 06:19 AM
#63
Re: Help using JSON API that requires Authentication with JWT token
Some names are reserved words for VB.NET so you have to use JsonProperty attribute and rename the variable to something not reserved. For example:
VB.NET Code:
Public Class OriginalCountry
Public Property String As String
Public Property Valid As Boolean
End Class
will become:
VB.NET Code:
Public Class OriginalCountry
<JsonProperty("String")> Public Property Country As String
Public Property Valid As Boolean
End Class
The attribute JsonProperty is telling Newtonsoft.Json library to put the value of String JSON property to OriginalCountry.Country property.
This way you have to decorate all badly named JSON properties to something valid and more meaningful in your VB.NET classes. Rename the class Alias as it is also reserved word.
It is also good style to rename _all_ classes and properties that are not in line with the rules you use in your VB.NET apps, e.g. class name and properties should use PascalCase naming convention. Meaningful names will make further development much easier as OriginalCountry.Country or FirstAired.FirstAiredDate (just put my own ones as I don't know what it is about) are better than using Country.String or FirstAired.String.
If you want you can add conversion of strings to real dates in your properties by converting them to full properties with getter and setter and put the date to Date or DateTimeOffset if you are converting from Unix time milliseconds:
VB.NET Code:
Dim firstAired As DateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(123546)
Dim firstAiredDate As Date = DateTimeOffset.FromUnixTimeMilliseconds(123546).DateTime
-
Mar 28th, 2023, 02:48 AM
#64
Thread Starter
Member
Re: Help using JSON API that requires Authentication with JWT token
Originally Posted by Sitten Spynne
OK. We know how to convert objects to and from JSON. That's important to this project because any web API is mostly about sending and receiving JSON objects.
I like to work in layers. To log in, I need to send some particular JSON to the server, and it's going to give me some JSON back. I don't want to muck about with HTTP types every time I do that. So I'm going to write a "RestClient" class to handle the HTTP bits for me. Let's have a look at the first draft:
Code:
Imports System.IO
Imports System.Net
Public Class RestClient
Public Function Post(ByVal url As String, ByVal payload As String) As String
' Configure the request headers.
Dim request As HttpWebRequest = CType(HttpWebRequest.Create(url), HttpWebRequest)
request.Method = "POST"
request.ContentType = "application/json"
' Send the request plus the JSON payload.
Using requestStream As Stream = request.GetRequestStream()
Using writer As New StreamWriter(requestStream, Text.Encoding.UTF8)
writer.Write(payload)
End Using
End Using
' Read the response and its JSON payload.
Using response As HttpWebResponse = CType(request.GetResponse(), HttpWebResponse)
If response.StatusCode <> HttpStatusCode.OK Then
' This needs to be more sophisticated, you can flesh it out later.
Throw New ApplicationException("There was an error with the request.")
End If
Using responseStream As Stream = response.GetResponseStream()
Using reader As New StreamReader(responseStream, Text.Encoding.UTF8)
Return reader.ReadToEnd()
End Using
End Using
End Using
End Function
End Class
This method takes the URL and some JSON. It sets up an HTTP POST request and sends the JSON along with it. It then reads the response, throws an exception if there was an error, or returns the JSON payload of the response.
Why am I leaving the JSON as a string here? Why am I not using objects? Life is easiest if each method does only one thing. This method "sends a request and returns the response". That's about as close to "one thing" as an HTTP exchange can get. If I made a more generic version that worked with objects, it would, "Serialize an object to JSON, then send it as part of a REST request, then attempt to deserialize the response and return the deserialized object." That's more complicated. More complicated things are more likely to fail, and when they fail they're harder to debug.
So I've got this RestClient class. How's this help me do something with the API? Well, I want to make using the API pretty easy. So I'd like a class that doesn't look like HTTP at all, and instead works with the objects I need to make to represent the JSON. When I look at the /login endpoint, I can describe it as "a function that takes some credentials and returns a JWT". In VB terms:
Code:
Public Function Login(ByVal credentials As TvDbCredentials) As String
But wait. The '/login/ endpoint doesn't return a String. It returns a JSON string representing an object with a 'token' property (unless there's an error.) So I need to make an object to represent it:
Code:
Public Class LoginResult
Public Property token As String
End Class
Armed with that, I can write a function that converts the TvDbCredentials to JSON, sends it to the /login endpoint, and returns the token from the result:
Code:
Imports Newtonsoft.Json
Public Class TvDbApi
Public Function Login(ByVal credentials As TvDbCredentials) As String
Dim restClient As New RestClient()
Dim url As String = "https://example.com/login"
Dim payloadJson As String = JsonConvert.SerializeObject(credentials)
Dim responseJson As String = restClient.Post(url, payloadJson)
Dim responseObject As LoginResult = JsonConvert.DeserializeObject(Of LoginResult)(responseJson)
Return responseObject.token
End Function
End Class
See how small, neat, and easy to understand it is with the HTTP logic removed? At this point you ought to be able to get a JWT with a small test application:
Code:
Module Module1
Sub Main()
Dim credentials As New TvDbCredentials()
credentials.apikey = "apikey asdf"
credentials.username = "username asdf"
credentials.userkey = "userkey asdf"
Dim tvDbApi As New TvDbApi()
Dim jwt As String = tvDbApi.Login(credentials)
Console.WriteLine(jwt)
End Sub
End Module
See the layers at work? You can log in and get the JWT without thinking about JSON or HTTP now. This is another good place for me to stop. Try this out. Make sure to change the URL from 'example.com' to the right one for TvDb, and make sure you know if you should be using https or just http. If the program prints a JWT, you're doing great and can continue. If there is an error, we have to fix that before moving on.
The next post is going to try out a random entry point to demonstrate how we can take the code we already have and adjust it to include a JWT.
Sitten Spynne,
Hello, I hope you are still active in this forum. You have helped me transition my program from using an XML to JSON API from the TVdb back in 2017 and the program has been working like a charm all this time. That was version 2.0 or the API. The Tvdb.com is eliminating the old 2.0API very soon as they have rolled out version 4.0. The new version has a ton of changes including the addition on movies, but the 2.0 API key has been replaced by a developer key unique to a program as well as a user pin. I have already made these changes and I was able to get the JWT token in the early staged of 4.0 API. As the new API became finalized I was no longer able to get a valid token. I now get an error in ResrClient that I can't seem to get past (Could not create SSL/TSL secure channel). I am not sure if maybe Newtonsoft.json or VisualBasic.net needs some update to work with a secure API, or if it even has something to do with increased security. I hope you or someone can point me in the right direction.
Thanks,
Jim
-
Mar 28th, 2023, 09:03 AM
#65
Re: Help using JSON API that requires Authentication with JWT token
Please open a new thread and link to this one.
Tags for this Thread
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
|