שתף באמצעות


Using NewtonSoft.JSON to get data into VB.NET structures/classes (long post)

Question

Friday, December 4, 2015 3:34 PM

Hi

I am currently struggling to understand and deserialize some json returned from the following link:

https://atlas.metabroadcast.com/3.0/channel_groups/cbhJ.json?apiKey=\*\*\*\*\*\*\*&annotations=channels

it is a longish piece of data, so here is an extract showing the data I am trying to extract (channel_group: 'title' and 'id', and however many channel: data)

{
    "channel_groups": 
[
{
    "publisher": 
{
    "key": "metabroadcast.com",
    "name": "UK Channel Lineups",
    "country": "ALL"
},
"title": "Scotland (Central)",
"available_countries": 
[
    "GB"
],
"channels": 
[
{
    "channel": 
{
    "publisher": 
{

    "key": "metabroadcast.com",
    "name": "UK Channel Lineups",
    "country": "ALL"

},
"title": "BBC One Scotland",
"image": "http://images.atlas.metabroadcast.com/pressassociation.com/channels/p131774.png",
"images": 
[
{
    "image_type": "logo",
    "color": "color",
    "theme": "light_transparent",
    "width": ​320,
    "height": ​180,
    "aliases": [ ],
    "v4_aliases": [ ],
    "uri": "http://images.atlas.metabroadcast.com/pressassociation.com/channels/p2201211212.png"
},
{
    "image_type": "logo",
    "color": "color",
    "theme": "light_opaque",
    "width": ​320,
    "height": ​180,
    "aliases": [ ],
    "v4_aliases": [ ],
    "uri": "http://images.atlas.metabroadcast.com/pressassociation.com/channels/p131774.png"
},
    {
        "image_type": "logo",
        "color": "color",
        "theme": "dark_transparent",
        "width": ​320,
        "height": ​180,
        "aliases": [ ],
        "v4_aliases": [ ],
        "uri": "http://images.atlas.metabroadcast.com/pressassociation.com/channels/p1201211212.png"
    }
],
"media_type": "video",
"high_definition": false,
"regional": true,
"broadcaster": 
{
    "key": "bbc.co.uk",
    "name": "BBC",
    "country": "GB"
},
"available_from": 
[

{
    "key": "bbc.co.uk",
    "name": "BBC",
    "country": "GB"
},
    {
        "key": "pressassociation.com",
        "name": "PA",
        "country": "GB"
    }
],
"parent": 
{
    "title": "BBC One",
    "related_links": [ ],
    "genres": [ ],
    "aliases": [ ],
    "v4_aliases": [ ],
    "id": "cbPF"
},
"related_links": 
[
    {
        "type": "simulcast",
        "url": "http://www.bbc.co.uk/tv/bbcone/live"
    }
],
"start_date": "1999-01-01T00:00:00Z",
"genres": 
[
    "http://pressassociation.com/genres/entertainment"
],
"aliases": 
        [
            "bbcone-scotland",
            "http://pressassociation.com/channels/2",
            "http://xmltv.radiotimes.com/channels/101",
            "http://youview.com/service/1129",
            "http://atlas.metabroadcast.com/4.0/channels/hkq6"
        ],
        "v4_aliases": [ ],
        "uri": "http://www.bbc.co.uk/services/bbcone/scotland",
        "id": "cbbv",
        "type": "channel"
    },
    "channel_number": "101",
    "start_date": "2014-03-25T00:00:00Z"

},
{"channel": {  .......  etc for multiple 'channel'

and at the bottom of entire file:

            "v4_aliases": [ ],
            "uri": "http://ref.atlasapi.org/regions/pressassociation.com/4-123",
            "id": "cbhJ",
            "type": "region"
        }
    ]
}

Here is a function I managed to get to work on some other data from same site:

Public Function GetProviders() As List(Of groups)
        Dim p As New List(Of groups)
        Dim src As String = "http://atlas.metabroadcast.com/3.0/channel_groups.json?type=platform"
        Dim client As New Net.WebClient()
        Dim stream As IO.Stream = client.OpenRead(src)
        Dim reader As New IO.StreamReader(stream)
        Dim jsonData As String = reader.ReadToEnd
        reader.Close()

        Dim allData As JObject = JObject.Parse(jsonData)

        For Each token As JToken In allData("channel_groups")
            Dim gr As New groups
            Dim nr As New region
            gr.groupname = token("title").ToString & " ( " & token("id").ToString & " )"
            gr.groupID = token("id").ToString

            Dim r As IList = CType(token("regions"), IList)
            For Each i As JToken In r
                gr.regions.Add(New region With {.regionID = i("id").ToString, .regionname = i("title").ToString & " ( " & i("id").ToString & " )"})
            Next
            p.Add(gr)
        Next
        Return p
    End Function

I have tried all sorts of google searches etc and can not find any examples that seem to fit what I need. I am hoping there are some users here who can offer some assistance.

Regards Les, Livingston, Scotland

All replies (21)

Friday, December 4, 2015 6:21 PM ✅Answered | 2 votes

If you don't need the entire object hierarchy and just want to extract some particular values then you might start with code something like:

Option Strict On

Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Imports System.Net.Http
Imports System.IO

Module Module1
    Sub Main()
        Dim t = JsonTestAsync()
        Console.ReadKey()
    End Sub

    Private Async Function JsonTestAsync() As Task
        Using client As New HttpClient
            Dim response = Await client.GetAsync("https://atlas.metabroadcast.com/3.0/channel_groups/cbhJ.json?apiKey={insert your API key}&annotations=channels")
            Using reader As New StreamReader(Await response.Content.ReadAsStreamAsync)
                Using jsonreader = New JsonTextReader(reader)
                    Dim serializer = New JsonSerializer()
                    Dim document As JObject = CType(serializer.Deserialize(jsonreader), JObject)
                    For Each result In document
                        Console.WriteLine(result.Key)
                        For Each channelgroup In result.Value
                            Console.WriteLine("Title = " & channelgroup.Item("title").ToString)
                            Console.WriteLine("ID = " & channelgroup.Item("id").ToString)
                            For Each channel In channelgroup.Item("channels")
                                Dim first = channel.Children.First
                                Dim titleInfo = first.Children.First
                                Dim numberInfo = first.Next
                                Console.WriteLine(numberInfo.Children.First.ToString & " = " & titleInfo.Item("title").ToString)
                            Next
                        Next
                    Next
                End Using
            End Using
        End Using
    End Function
End Module

Depending on how much of the model you plan to consume, cherry picking values out of the stream may be quicker/easier than building the entire document model.

Reed Kimble - "When you do things right, people won't be sure you've done anything at all"


Friday, December 4, 2015 4:36 PM | 1 vote

Les,

Several months ago I posted this which uses NewtonSoft.JSON.

You might want to have a look and see if that'll help.

If I had eight hours to chop down a tree, I'd spend six sharpening my axe. -- Abraham Lincoln


Friday, December 4, 2015 4:59 PM

You have to make a custom object or objects with properties that match the properties of the JSON object. You may need to use a List(of T) in the parent object, since it looks like you have arrays in the JSON object.

I don't know what all the stream stuff is about, because the src has the JSON and that's all you need is the string variable.

https://www.youtube.com/watch?v=ly3ivuQUgB0

http://www.codeproject.com/Tips/79435/Deserialize-JSON-with-C

https://msdn.microsoft.com/en-us/library/dd293589.aspx?f=255&MSPPError=-2147217396


Friday, December 4, 2015 6:28 PM | 1 vote

Les,

Until I looked at Reed's post, I didn't realize that you have your API key in the URL.

I'd suggest that you remove that!

If I had eight hours to chop down a tree, I'd spend six sharpening my axe. -- Abraham Lincoln


Friday, December 4, 2015 6:28 PM

Hi

I have downloaded your code Frank, and will need to spend some time to digest it.

DA924 - the streaming is purely my way of fetching the data from the web site. The function I posted is very poor code as it is the result of much trial and error and just ended up that way. If I manage to get all the way through, I would clean everything up.

My overall objective: I fetch the data for Providers and extract a set of  title/id/regions for each provider - populate 2 CB with those (done and working). A third CB would be populated with the channel data which is the data I am struggling with. I get the Provider/Region OK which provides title/id for the needed channels, but here is where I have hit the wall. If I manage to get the channel data then my next step would be to fetch the schedule data (yet another brick wall facing me:)

I can create class(es) as needed, but since I am trying to get partial data from the (well over 100 channels) I am well and truly stuck.

'You have to make a custom object or objects with properties that match the properties of the JSON object.'

That is one area that I am struggling with as I can't sort out how to 'isolate' the required data from the data I don't need from the JSON string.

Regards Les, Livingston, Scotland


Friday, December 4, 2015 6:32 PM

Les,

Until I looked at Reed's post, I didn't realize that you have your API key in the URL.

I'd suggest that you remove that!

If I had eight hours to chop down a tree, I'd spend six sharpening my axe. -- Abraham Lincoln

Thanks for the reminder - I wouldn't have been able to test the document structure without it, but I've edited the sample code to remove the API key.

Reed Kimble - "When you do things right, people won't be sure you've done anything at all"


Friday, December 4, 2015 6:55 PM

Hi

I have downloaded your code Frank, and will need to spend some time to digest it.

DA924 - the streaming is purely my way of fetching the data from the web site. The function I posted is very poor code as it is the result of much trial and error and just ended up that way. If I manage to get all the way through, I would clean everything up.

My overall objective: I fetch the data for Providers and extract a set of  title/id/regions for each provider - populate 2 CB with those (done and working). A third CB would be populated with the channel data which is the data I am struggling with. I get the Provider/Region OK which provides title/id for the needed channels, but here is where I have hit the wall. If I manage to get the channel data then my next step would be to fetch the schedule data (yet another brick wall facing me:)

I can create class(es) as needed, but since I am trying to get partial data from the (well over 100 channels) I am well and truly stuck.

'You have to make a custom object or objects with properties that match the properties of the JSON object.'

That is one area that I am struggling with as I can't sort out how to 'isolate' the required data from the data I don't need from the JSON string.

Regards Les, Livingston, Scotland

Well, stop using the stream stuff. It's all there in the string variable.

Query the JSON string with Linq then and return a result of a anonymous objects with properties of data that you want -- project out and shape what you want a linq projection.

http://www.newtonsoft.com/json/help/html/QueryingLINQtoJSON.htm

Find out how to use Linq projection in VB using anonymous objects. I am tired of looking for VB stuff.

https://msdn.microsoft.com/en-us/library/bb384767.aspx

https://msdn.microsoft.com/en-us/library/bb763068.aspx


Friday, December 4, 2015 8:00 PM

Les,

Until I looked at Reed's post, I didn't realize that you have your API key in the URL.

I'd suggest that you remove that!

If I had eight hours to chop down a tree, I'd spend six sharpening my axe. -- Abraham Lincoln

Hi

Thanks Frank - key obscured

Regards Les, Livingston, Scotland


Friday, December 4, 2015 8:35 PM

If you don't need the entire object hierarchy and just want to extract some particular values then you might start with code something like:

Option Strict On

Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Imports System.Net.Http
Imports System.IO

Module Module1
    Sub Main()
        Dim t = JsonTestAsync()
        Console.ReadKey()
    End Sub

    Private Async Function JsonTestAsync() As Task
        Using client As New HttpClient
            Dim response = Await client.GetAsync("https://atlas.metabroadcast.com/3.0/channel_groups/cbhJ.json?apiKey={insert your API key}&annotations=channels")
            Using reader As New StreamReader(Await response.Content.ReadAsStreamAsync)
                Using jsonreader = New JsonTextReader(reader)
                    Dim serializer = New JsonSerializer()
                    Dim document As JObject = CType(serializer.Deserialize(jsonreader), JObject)
                    For Each result In document
                        Console.WriteLine(result.Key)
                        For Each channelgroup In result.Value
                            Console.WriteLine("Title = " & channelgroup.Item("title").ToString)
                            Console.WriteLine("ID = " & channelgroup.Item("id").ToString)
                            For Each channel In channelgroup.Item("channels")
                                Dim first = channel.Children.First
                                Dim titleInfo = first.Children.First
                                Dim numberInfo = first.Next
                                Console.WriteLine(numberInfo.Children.First.ToString & " = " & titleInfo.Item("title").ToString)
                            Next
                        Next
                    Next
                End Using
            End Using
        End Using
    End Function
End Module

Depending on how much of the model you plan to consume, cherry picking values out of the stream may be quicker/easier than building the entire document model.

Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

Hi Reed

I missed your post until it was referred to further on.

I can't get your code to compile.

1.  Imports System.Net.Http is not valid. Http - ERROR: namespace or type not found or no public members ........

2.     Private Async Function JsonTestAsync() As Task - ERROR:

lacks an 'await' operator so will run syncronously ......

3.         Using client As New HttpClient - ERROR: Type HttpClient not defined .....

Regards Les, Livingston, Scotland


Friday, December 4, 2015 8:41 PM

Hi

I have downloaded your code Frank, and will need to spend some time to digest it.

DA924 - the streaming is purely my way of fetching the data from the web site. The function I posted is very poor code as it is the result of much trial and error and just ended up that way. If I manage to get all the way through, I would clean everything up.

My overall objective: I fetch the data for Providers and extract a set of  title/id/regions for each provider - populate 2 CB with those (done and working). A third CB would be populated with the channel data which is the data I am struggling with. I get the Provider/Region OK which provides title/id for the needed channels, but here is where I have hit the wall. If I manage to get the channel data then my next step would be to fetch the schedule data (yet another brick wall facing me:)

I can create class(es) as needed, but since I am trying to get partial data from the (well over 100 channels) I am well and truly stuck.

'You have to make a custom object or objects with properties that match the properties of the JSON object.'

That is one area that I am struggling with as I can't sort out how to 'isolate' the required data from the data I don't need from the JSON string.

Regards Les, Livingston, Scotland

Well, stop using the stream stuff. It's all there in the string variable.

Query the JSON string with Linq then and return a result of a anonymous objects with properties of data that you want -- project out and shape what you want a linq projection.

http://www.newtonsoft.com/json/help/html/QueryingLINQtoJSON.htm

Find out how to use Linq projection in VB using anonymous objects. I am tired of looking for VB stuff.

https://msdn.microsoft.com/en-us/library/bb384767.aspx

https://msdn.microsoft.com/en-us/library/bb763068.aspx

Well, you have me lost (easily done). I need to fetch the data somehow! The 'src' variable is just the web address.

Regards Les, Livingston, Scotland


Friday, December 4, 2015 9:05 PM

Hi

I have downloaded your code Frank, and will need to spend some time to digest it.

DA924 - the streaming is purely my way of fetching the data from the web site. The function I posted is very poor code as it is the result of much trial and error and just ended up that way. If I manage to get all the way through, I would clean everything up.

My overall objective: I fetch the data for Providers and extract a set of  title/id/regions for each provider - populate 2 CB with those (done and working). A third CB would be populated with the channel data which is the data I am struggling with. I get the Provider/Region OK which provides title/id for the needed channels, but here is where I have hit the wall. If I manage to get the channel data then my next step would be to fetch the schedule data (yet another brick wall facing me:)

I can create class(es) as needed, but since I am trying to get partial data from the (well over 100 channels) I am well and truly stuck.

'You have to make a custom object or objects with properties that match the properties of the JSON object.'

That is one area that I am struggling with as I can't sort out how to 'isolate' the required data from the data I don't need from the JSON string.

Regards Les, Livingston, Scotland

Well, stop using the stream stuff. It's all there in the string variable.

Query the JSON string with Linq then and return a result of a anonymous objects with properties of data that you want -- project out and shape what you want a linq projection.

http://www.newtonsoft.com/json/help/html/QueryingLINQtoJSON.htm

Find out how to use Linq projection in VB using anonymous objects. I am tired of looking for VB stuff.

https://msdn.microsoft.com/en-us/library/bb384767.aspx

https://msdn.microsoft.com/en-us/library/bb763068.aspx

Well, you have me lost (easily done). I need to fetch the data somehow! The 'src' variable is just the web address.

Regards Les, Livingston, Scotland

JSON is string regardless of whatever variable it would up in just query it with Linq. It is simple.


Friday, December 4, 2015 9:08 PM

JSON is string regardless of whatever variable it would up in just query it with Linq. It is simple.

Hi

No, I am unsure of what you mean. I do  actually need to fetch the data, it won't come to me by magic.

Regards Les, Livingston, Scotland


Friday, December 4, 2015 9:17 PM

https://www.daniweb.com/programming/software-development/threads/475686/how-to-retrieve-data-from-url-into-string-json

This is really all you needed to do to get the JSON into a string variable. Then it would a simple task to query the data in the string variable using Linq-2-JSON and using a LINQ projection.

http://www.newtonsoft.com/json/help/html/linqtojson.htm

You want to go the hard way to the moon and back on something so simple.


Friday, December 4, 2015 9:20 PM

I will say that most of this stuff with examples an all are really easy to fine that in C#, but not VB.NET. But you can see the example in C# and see how easy it is and write it in VB,NET


Friday, December 4, 2015 9:27 PM

Les,

Did you know that you can also get an XML version of that? To do that, in your actual full url, you'll see "=json" ... change that to "=xml".

I say that because looking at the XML, the structure is more obvious (or it is to me anyway).

*****

I see the id that you asked for but I don't see "title" and I'm not sure what you mean about "data" either, but this might get you started - using XML. You can then later work out the JSON using the Newton assembly but you first need to work out just what you want.

In a new project, give this a go as a start then examine the XML returned when it gets to "Stop" below:

Option Strict On
Option Explicit On
Option Infer Off

Imports <xmlns:play="http://ref.atlasapi.org/">

Public Class Form1
    Private Sub Form1_Load(sender As System.Object, _
                           e As System.EventArgs) _
                           Handles MyBase.Load

        Const url As String = "xml_URL_Here"

        Dim xDoc As XElement = XElement.Load(url)

        For Each group As XElement In xDoc...<play:channel_group>
            Dim id As String = group...<id>.Value

            Stop
        Next

        Stop

    End Sub
End Class

If I had eight hours to chop down a tree, I'd spend six sharpening my axe. -- Abraham Lincoln


Friday, December 4, 2015 9:34 PM

Hi Les,

System.Net.Http is in framework 4.5+ so I have to assume you are targeting an earlier version of the framework.  Error's 2 and 3 are the result of 1.

If you can move to Visual Studio 2015 then you can use the sample as-is.  Otherwise you'll need to factor out the HttpClient and use a System.Net.HttpWebRequest instead (or other means of downloading the data into a string).

Reed Kimble - "When you do things right, people won't be sure you've done anything at all"


Friday, December 4, 2015 9:42 PM | 2 votes

Les (yes, I know I'm replying to Reed, but this is a follow-up),

If you want to try Reed's method - and I would encourage you to because it'll be a LOT faster than XML - then instead of "downloading" it, per se, you can get the string (all of it):

Imports System.IO
Imports System.Net
    Private Function ReturnTextFromURL(ByVal url As String) As String
        
        Dim retVal As String = ""
        
        Try
            Dim request As WebRequest = WebRequest.Create(url)
            
            Using response As HttpWebResponse = DirectCast(request.GetResponse, HttpWebResponse)
                Using dataStream As Stream = response.GetResponseStream
                    Using reader As New StreamReader(dataStream)
                        Dim responseFromServer As String = reader.ReadToEnd()
                        retVal = responseFromServer
                    End Using
                End Using
            End Using
            
        Catch ex As Exception
            retVal = ""
        End Try
        
        Return retVal
        
    End Function

If I had eight hours to chop down a tree, I'd spend six sharpening my axe. -- Abraham Lincoln


Saturday, December 5, 2015 12:11 AM

Hi Les,

System.Net.Http is in framework 4.5+ so I have to assume you are targeting an earlier version of the framework.  Error's 2 and 3 are the result of 1.

If you can move to Visual Studio 2015 then you can use the sample as-is.  Otherwise you'll need to factor out the HttpClient and use a System.Net.HttpWebRequest instead (or other means of downloading the data into a string).

Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

Hi Reed

Yes, I am using Net 4.5.1on windows 8.1 with VB 2013

I can get the string OK, but right now, I don't have enough time to delve too deeply. Things might ease a little tomorrow. I will try it out though.

Regards Les, Livingston, Scotland


Saturday, December 5, 2015 3:12 PM

Hi one and all

I have made some progress, thanks to contributions here. This is the code so far which lets me choose a Provider and Region, and from those, get the appropriate channels. Now, I will need to deal with getting the EPG data for user selected channels. (maybe DA924 will approve of my using Franks code to fetch the data)

Code so far

Option Strict On
Option Explicit On
Option Infer Off
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Imports System.Net
Imports System.IO
Module GetData
    Public providers As New List(Of groups)
    Public Function GetProviders() As List(Of groups)
        ' provides the data to populate CB1 and CB2
        Dim p As New List(Of groups)
        Dim src As String = ReturnTextFromURL("http://atlas.metabroadcast.com/3.0/channel_groups.json?type=platform")
        Dim allData As JObject = JObject.Parse(src)
        For Each token As JToken In allData("channel_groups")
            Dim gr As New groups
            Dim nr As New region
            gr.groupname = token("title").ToString & " ( " & token("id").ToString & " )"
            gr.groupID = token("id").ToString

            Dim r As IList = CType(token("regions"), IList)
            For Each i As JToken In r
                gr.regions.Add(New region With {.regionID = i("id").ToString, .regionname = i("title").ToString & " ( " & i("id").ToString & " )"})
            Next
            p.Add(gr)
        Next
        Return p
    End Function
    Public Function FetchChannels(region As String) As List(Of channel)
        ' provides Channel List for DGV based on Provider and Region
        ' in CB1 and CB2   thanks to Frank and Reed
        Dim Channels As New List(Of channel)
        Dim response As String = ReturnTextFromURL("https://atlas.metabroadcast.com/3.0/channel_groups/" & region & ".json?apiKey=[API KEY]&annotations=channels")
        Dim alldata As JObject = JObject.Parse(response)
        For Each result As KeyValuePair(Of String, JToken) In alldata
            For Each channelgroup As JToken In result.Value
                For Each channel As JToken In channelgroup.Item("channels")
                    Dim chdata As JToken = channel.Children.First.Children.First
                    Dim logo As String = Nothing
                    Try
                        logo = chdata.Item("image").ToString()
                    Catch ex As Exception
                        logo = Nothing
                    End Try
                    Dim ch As New channel With {.ID = chdata.Item("id").ToString, .Name = chdata.Item("title").ToString, .Chan = channel.Item("channel_number").ToString, .logo = logo}
                    Channels.Add(ch)
                Next
            Next
        Next
        Return Channels
    End Function
    Private Function ReturnTextFromURL(ByVal url As String) As String
        ' provides JSON string to ther functions   thanks Frank
        Dim retVal As String = Nothing
        Try
            Dim request As WebRequest = WebRequest.Create(url)
            Using response As HttpWebResponse = DirectCast(request.GetResponse, HttpWebResponse)
                Using dataStream As Stream = response.GetResponseStream
                    Using reader As New StreamReader(dataStream)
                        Dim responseFromServer As String = reader.ReadToEnd()
                        retVal = responseFromServer
                    End Using
                End Using
            End Using
        Catch ex As Exception
            retVal = Nothing
        End Try
        Return retVal
    End Function
End Module
Class groups
    Property groupname As String
    Property groupID As String
    Property regions As New List(Of region)
End Class
Class region
    Property regionID As String
    Property regionname As String
End Class
Class channel
    Property USE As Boolean = False
    Property Chan As String = Nothing
    Property ID As String = Nothing
    Property Name As String = Nothing
    Property logo As String = Nothing
End Class

Regards Les, Livingston, Scotland


Monday, December 7, 2015 6:21 PM

If it works for go with it, since I am posting from a hospital bed as I will tore up my knee this past Friday.


Wednesday, December 9, 2015 10:03 PM

So, I've been playing with this some more and have come up with something which I like much better than Newtonsoft.JSON for dealing with JSON documents when you don't exactly know the document model.  I've created a thread about it here.

I've come to see how you could use LINQ to get anonymous objects representing the document model using Newtonsoft.JSON, but it can still be tricky to figure out the document model in the first place so that you can construct an accurate LINQ statement.

I've tried the code in that other thread against your data source (sorry, I'll stop using your API key now! lol) and it works very well.  The resulting object model is easy to understand and work with.  I would encourage you to try that code with this JSON data.

Reed Kimble - "When you do things right, people won't be sure you've done anything at all"