Scope


This article presents an approach to deserializing JSON from a Web service and binding the result to a WPF ListBox.  Thus providing a simple read only user interface. We will use Visual Basic .NET, the well known Newtonsoft.Json library and a pinch of XAML for the UI layer.

Our goal


Overall, nothing too complex, and at the same time something useful and fun - at least for TechNet users. We'll make a program which allows a TechNet and/or MSDN user to consult his profile desktop-side, retrieving data from the Web and presenting it on the desktop  In particular, we will focus on TechNet's medals, retrieving information about a specific user's state.

Prerequisites


Before we begin to develop our project, we need to know two essential pieces of information, namely: 1) which is the address to query in order to retrieve the TechNet medals detail for a given user, and 2) how to uniquely identify our specific user. To this end, it is necessary to open a browser with the TechNet profile page for that user, and search - in its HTML code - for the script loadReputation().



In the picture you can see how the script works on a specific URL. By copying this address in a new browser page, we see the following output serialized, which corresponds to the basic information of the user (Id, current points, etc.), and the medals details of the medals held at the moment, listed by type.



Now we know the URL from to query to retrieve the serialized information of interest, namely: https://api.recognition.microsoft.com/v1/user/<USER-ID>/detail?Locale=<LOCAL>, where - of course - the <USER-ID> and <LOCAL> must be replaced with the correct values: the first, must represent a valid user (and to know our ID or another user's, we must always search for it in the HTML code of the profile), while the second is the language in which we want our results to be retrieved (I will use the local "en-US" for the sake of translation).

The recovered data must be deserialized, i.e. it must be translated into the more usable form of objects. This is necessary since there is no way to directly present json to the user other than as a rather unintelligible string. As has been mentioned, we'll use the Newtonsoft.Json library, which provides quick tools and classes for serialization/deserialization purposes. The library is available for download at https://json.codeplex.com/. Let's download it, to see in a moment how it can be referenced in the project.

At this point, we have all what we need to proceed in developing our program..

Referencing Newtonsoft.Json DLL


Inside the ZIP package (downloaded from https://json.codeplex.com/) we'll find several folders. You'll need to unzip the file Newtonsoft.Json.dll from the folder Bin/<VERSION>, where <VERSION> identifies the folder containing a version of the DLL compatible with the version of the .NET Framework that we are going to use. In my case, the DLL has been extracted from the Bin/Net45 folder, because 4.5 is the version of the Framework that i will employ in the program. The DLL can be saved in any path: for convenience, i will create a directory named "thirdparts" as a subfolder in my project, to keep things close.



Open Visual Studio and create a new Visual Basic WPF project. To reference the library you just saved, we will have to click on "Project"> "Add Reference". This will open a form in which we can search for a particular file, select it, and make it available to the project. Let's proceed searching and referencing Newtonsoft.Json.dll.





After that, the namespace Newtonsoft.Json will become available in our project. From it, we will use the method DeserializeObject, belonging to the JsonConvert class. But first, we have to query the Web site to obtain, in json/text format, the serialized information to work on.

Querying Web page


Since we do not need anything too complex, having to only retrieve the response of a Web page, we will use the WebClient class and its function DownloadString. Assuming our identification code is contained in the string variable userId, the JSON stream can be recovered with the following instructions:

Dim tnaddress As String = "https://api.recognition.microsoft.com/v1/user/" & userId & "/ detail? Locale = en-US"
Dim As New WebClient System.Net.WebClient
Dim result As String = webClient.DownloadString (tnaddress)

We prepare the URL to be queried, passing it as an argument for the function DownloadString, which will create the connection and read data in response, making them available for assignment to a variable. Now, starting from the variable result, we can proceed with its deserialization, in order to 'unpack' the JSON stream.

Deserializing JSON


The function JsonConvert.DeserializeObject can be used on typed structures. Therefore we will have to define one or more classes conformed to the structure of the flow to deserialize. In image 2, we note that there is a "root" level in our stream, with the variables UserId, Points, Percentile, GoldAchievements, SilverAchievements, BronzeAchievements. The last three defines three separate classes of medals: gold, silver, bronze. Each contains different entities, i.e. Title, Description, AchievementId, Earned.

To make that kind of structure in a class format, we can sketch out something like this:

Public Class UserFeed
    UserId As String Public Property
    Points As Long Public Property
    Percentile As Integer Public Property
    Public Property GoldAchievements As Medal ()
    Public Property SilverAchievements As Medal ()
    Public Property BronzeAchievements As Medal ()
End Class
 
Public Class Medal
    Public Property Title As String
    Public Property Description As String
    As Integer Public Property AchievementId
    Earned As String Public Property
End Class

In other words, there is a "base" class,  which we called UserFeed. Inside it, we define the properties UserId, Points, Percentile as standard types, while GoldAchievements, SilverAchievements, BronzeAchievements, referencing the same properties, can be traced to a generic class that we'll call Medal, in which we define the properties seen above, or: Title , Description, AchievementId, Earned.

Once we've finished in preparing the class that will host deserialized data, the actual process is carried out with a single line of code:

Dim uf As UserFeed = JsonConvert.DeserializeObject (Of UserFeed) (result)

That is, given a variable uf, defined as class UserFeed, its properties will be valued using the function DeserializeObject, applied to the type UserFeed, using as input parameter in the JSON string (i.e., the variable previously set by reading the Web page). From now on, any reference to the variable uf (and its properties), will expose the contents of variables as well as derived from JSON Web response.

If we want to display in a single container all the medals, whatever the type, we need to unify the three arrays of type Medal in one list. At the same time, we will have to give this list a new property, which allows to discern the type of medal (if gold, silver, bronze), information that we haven't in the three initial arrays, if not at the property name level. Although there are more elegant methods, a quick workaround may be as follows.

Prepare data structure with LINQ


Since the variable uf (as we saw above, it contains the deserialized data relating to a given TechNet profile), we could make a unique structure of medals belonging to the various types, by doing the following:

We'll create a new class that inherits the properties of the class Medal, adding a property named "Level", designed to define the type of a certain medal.

Public Class TypedMedal
    Inherits Medal
    As String Public Property Level
End Class

Next, we'll create a List(Of TypedMedal), and using three LINQ queries we will populate it with the elements of each medal's array. For each query, we will pass the value of the "Level" as fixed. In code, this results in:
 
MyMedals Dim As New List (Of TypedMedal)
myMedals.AddRange (From m As Medal In uf.GoldAchievements Select New With {TypedMedal .title = m.Title,
                                                                                     .description = M.Description,
                                                                                     .AchievementId = M.AchievementId,
                                                                                     .Earned = M.Earned,
                                                                                     .level = "Gold Achievements"})
 
myMedals.AddRange (From m As Medal In uf.SilverAchievements Select New With {TypedMedal .title = m.Title,
                                                                                       .description = M.Description,
                                                                                       .AchievementId = M.AchievementId,
                                                                                       .Earned = M.Earned,
                                                                                       .level = "Silver Achievements"})
 
myMedals.AddRange (From m As Medal In uf.BronzeAchievements Select New With {TypedMedal .title = m.Title,
                                                                                       .description = M.Description,
                                                                                       .AchievementId = M.AchievementId,
                                                                                       .Earned = M.Earned,
                                                                                       .level = "Bronze Achievements"})

Basically, using the method AddRange, we can add to our List(Of TypedMedal) an arbitrary number of elements of type TypedMedal. How do we get these items? Processing the three array of medals, which the uf variable contains. We'll analyze the first case only, since the other two follows the same logic.

From m As Medal In uf.GoldAchievements

The first part of the query is simple: we define a local variable m, of type Medal, to be used in looping elements contained by (In) GoldAchievements array, belonging to uf. We can say that this first part of the query corresponds perfectly to a For/Each loop, and in fact we can write it as:

For Each m As Medal In uf.GoldAchievements
    'Do something
Next

The second part of the query defines what should be done at each iteration of uf.GoldAchievements. In our case, we want to create a new entity of TypedMedal type, setting inherited properties with the values shared by Medal type, but assigning the to Level property, exclusive for TypedMedal, a fixed value (which, in this case, will be "Gold Achievements"). In other words, for each Medal element analyzed, a new element of TypedMedal type will be added to the list myMedals. It will contain the same information as Medal do, but also specifies the type of medal.
After performing the three queries, the myMedals list will be ready to be used as a view for a graphical control.

Binding to ListBox, and layout customization


Assuming to have a ListBox named MedalList on our WPF page, we want to bind it to the variable myMedals, to expose instances (and their properties) of which it is composed, providing a graphic layout through which the end user can consult it. At this point, we desire to divide the medals by type, to make clear which of them belongs to which category, i.e. gold, silver and bronze. First of all, let's see the code-behind, to further analyze the XAML aimed at achieving our final result.

Dim view As CollectionView = CollectionViewSource.GetDefaultView (myMedals)
view.GroupDescriptions.Add (New PropertyGroupDescription ("Level"))
MedalList.ItemsSource = view

The ItemsSource property allows to define the data collection to be used for a specific view. Since we want to expose our data grouping by type, we must declare a CollectionView with grouping on the "Level" property, defined in class TypedMedal. The CollectionView will have as starting data the content of List(Of TypedMedal) myMedals, and will define the internal PropertyGroupDescription field "Level". This view, as defined, will then be passed to the ItemsSource property of the ListBox, so that we can bind the data to the view.

In the XAML ListBox, we'll define two special sections: ItemTemplate, namely the definition of the graphic elements that will produce a single row in the list, and GroupStyle, or the controls through which we check (and show) the division between a group of elements and the next (when "Level" property changes, as previously defined in the view).

<ListBox Name = "medalist" HorizontalAlignment = "Stretch" Margin = "10,96,10,10" VerticalAlignment = "Stretch" Width = "Auto" AlternationCount = "2">
       <ListBox.ItemTemplate>
           <DataTemplate>
               <StackPanel Orientation = "Vertical" Margin = "0,6,5,2" Width = "auto">
                   <TextBlock FontSize = "14" FontWeight = "Bold" Foreground = "Black" Text = "{Binding Title}" Margin = "0,0,0,0" />
                   <TextBlock FontSize = "10" FontWeight = "Normal" Foreground = "Black" TextWrapping = "Wrap" FontStyle = "Italic" Text = "{Binding} Description"
                                 Margin = "0,2,0,0" />
               </ StackPanel>
           </ DataTemplate>
       </ListBox.ItemTemplate>
 
       <ListBox.GroupStyle>
           <GroupStyle>
               <GroupStyle.HeaderTemplate>
                   <DataTemplate>
                       <StackPanel Orientation = "Horizontal" Background = "LightBlue">
                           <TextBlock FontWeight = "Bold" FontSize = "15" Foreground = "Black" Text = "{Binding Name}" Margin = "5,4,10,4" />
                           <TextBlock FontWeight = "Normal" FontSize = "15" Foreground = "blue" Text = "{Binding} ItemCount" Margin = "0,4,0,0" FontStyle = "Italic" />
                       </ StackPanel>
                   </ DataTemplate>
               </GroupStyle.HeaderTemplate>
           </ GroupStyle>
       </ListBox.GroupStyle>
   </ ListBox>

Regarding the ItemTemplate section, a vertical StackPanel can be noticed inside it. The StackPanel contains two TextBlock, the first of which receives the binding of the Title property, while the second from the Description. Going back to the definition of the class Medal, and considering how data were populated from JSON, we can see how these two properties define, respectively, the name of the medal obtained and the description of the medal itself. We can therefore expect that every element of the ListBox is represented by a double line of text, with the first of these lines showing the title/name of the medal, and the second one showing the description.

The section GroupStyle indicates the details defining the group separator. In other words, given a property on which the presented data must be divided, here we'll define how that separator must be showed graphically. In our case, we will have one StackPanel again - horizontal, this time - with a light blue background. Inside of it, two TextBlocks (side by side due to the orientation of the StackPanel that contains them) in which we won't bind the specific properties of the data structure, but the properties of the PropertyGroupDescription we've defined in the code behind. Name is always referred to the value contained in the group (which in our case corresponds to the content of the property Level), while ItemCount defines the number of elements in that particular grouping.

Conclusive example


Applying the style above, and running our program, we will get a result like the following (in the image below, the query results corresponds to my profile, while i'm writing)




Class example


The complete source code of application is as follows:

Code-behind, MainWindow.xaml.vb

Imports System.Net
Imports Newtonsoft.Json
Imports System.ComponentModel
 
Class MainWindow
 
    Public Class UserFeed
        UserId As String Public Property
        Points As Long Public Property
        Percentile As Integer Public Property
        Public Property GoldAchievements As Medal ()
        Public Property SilverAchievements As Medal ()
        Public Property BronzeAchievements As Medal ()
    End Class
 
    Public Class Medal
        Public Property Title As String
        Public Property Description As String
        As Integer Public Property AchievementId
        Earned As String Public Property
    End Class
 
    Public Class TypedMedal
        Inherits Medal
        As String Public Property Level
    End Class
 
    Private Sub QueryTechnet (userId As String)
        If userId.CompareTo ("") = 0 Then
            MsgBox ("Please enter a valid UserId in the textbox", MsgBoxStyle.Exclamation)
            Exit Sub
        End If
 
        Dim tnaddress As String = "https://api.recognition.microsoft.com/v1/user/" & userId & "/ detail? Locale = en-US"
 
        Dim As New WebClient System.Net.WebClient
        Dim result As String = webClient.DownloadString (tnaddress)
 
        Dim uf As UserFeed = JsonConvert.DeserializeObject (Of UserFeed) (result)
 
 
        MyMedals Dim As New List (Of TypedMedal)
        myMedals.AddRange (From m As Medal In uf.GoldAchievements Select New With {TypedMedal .title = m.Title,
                                                                                             .description = M.Description,
                                                                                             .AchievementId = M.AchievementId,
                                                                                             .Earned = M.Earned,
                                                                                             .level = "Gold Achievements"})
 
        myMedals.AddRange (From m As Medal In uf.SilverAchievements Select New With {TypedMedal .title = m.Title,
                                                                                               .description = M.Description,
                                                                                               .AchievementId = M.AchievementId,
                                                                                               .Earned = M.Earned,
                                                                                               .level = "Silver Achievements"})
 
        myMedals.AddRange (From m As Medal In uf.BronzeAchievements Select New With {TypedMedal .title = m.Title,
                                                                                               .description = M.Description,
                                                                                               .AchievementId = M.AchievementId,
                                                                                               .Earned = M.Earned,
                                                                                               .level = "Bronze Achievements"})
 
 
        Dim view As CollectionView = CollectionViewSource.GetDefaultView (myMedals)
        view.GroupDescriptions.Add (New PropertyGroupDescription ("Level"))
        MedalList.ItemsSource = view
    End Sub
 
    Private Sub Button_Click (sender As Object, e As RoutedEventArgs)
        QueryTechnet (txtUserId.Text)
    End Sub
End Class

XAML, MainWindow.xaml

<Window x: Class = "MainWindow"
    Title = "My Technet Medals" Height = "550" width = "562" WindowStartupLocation = "CenterScreen" WindowStyle = "ThreeDBorderWindow" ResizeMode = "noresize">
 
    <Window.Resources>
        <Style TargetType = "{x: Type ListBoxItem}">
            <Style.Triggers>
                <Trigger Property = "ItemsControl.AlternationIndex" Value = "0">
                    <Setter Property = "Background" Value = "WhiteSmoke"> </ Setter>
                </ Triggers>
                <Trigger Property = "ItemsControl.AlternationIndex" Value = "1">
                    <Setter Property = "Background" Value = "White"> </ Setter>
                </ Triggers>
            </Style.Triggers>
        </ Style>
    </Window.Resources>
 
    <Grid>
 
        <ListBox Name = "medalist" HorizontalAlignment = "Stretch" Margin = "10,119,10,10" VerticalAlignment = "Stretch" Width = "Auto" AlternationCount = "2">
 
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation = "Vertical" Margin = "0,6,5,2" Width = "auto">
                        <TextBlock FontSize = "14" FontWeight = "Bold" Foreground = "Black" Text = "{Binding Title}" Margin = "0,0,0,0" />
                        <TextBlock FontSize = "10" FontWeight = "Normal" Foreground = "Black" TextWrapping = "Wrap" FontStyle = "Italic" Text = "{Binding} Description"
                                      Margin = "0,2,0,0" />
                    </ StackPanel>
                </ DataTemplate>
            </ListBox.ItemTemplate>
 
            <ListBox.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <StackPanel Orientation = "Horizontal" Background = "LightBlue">
                                <TextBlock FontWeight = "Bold" FontSize = "15" Foreground = "Black" Text = "{Binding Name}" Margin = "5,4,10,4" />
                                <TextBlock FontWeight = "Normal" FontSize = "15" Foreground = "blue" Text = "{Binding} ItemCount" Margin = "0,4,0,0" FontStyle = "Italic" />
                            </ StackPanel>
                        </ DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </ GroupStyle>
            </ListBox.GroupStyle>
 
        </ ListBox>
 
        <StackPanel HorizontalAlignment = "Left" Height = "109" Margin = "10,10,0,0" VerticalAlignment = "top" width = "529" Orientation = "Vertical">
            <Label Content = "Insert TechNet User Id, then press" Refresh "" FontWeight = "Bold" />
            <Label Content = "If you do not know what your User Id is, please read my article to find out!" />
            <TextBox Height = "23" TextWrapping = "Wrap" Name = "txtUserId" Text = "XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" />
            <Button Content = "Refresh" Margin = "415,0,0,0" Height = "30" Click = "Button_Click" />
 
        </ StackPanel>
    </ Grid>
</ Window>

Demo


The sample project can be downloaded at: https://code.msdn.microsoft.com/Parse-a-JSON-stream-to-b50e8a36

Other Languages