none
More JSON Woes RRS feed

  • Question

  • Trying to implement the Weather Underground AutoComplete, no API key needed. Here is the form:

    Here is the Code:

    Option Strict On
    Option Explicit On
    Option Infer Off
    Imports System.Text
    Imports System.IO
    Imports System.Net
    Imports Newtonsoft.Json
    
    Public Class Form1
        Dim JsonString As String
        Function GetRaw(ByVal SrchText As String) As String
            'http://autocomplete.wunderground.com/aq?query=Cuenca&h=1
            Dim sb As New System.Text.StringBuilder("http://autocomplete.wunderground.com/aq?query=")
            sb.Append(TXTSrch.Text)
            sb.Append("&h=1")
            JsonString = GetStringFromTxt(sb.ToString)
            Return JsonString
        End Function
    
        Function GetResultsFromJSONString(JSON As String) As Result()
            Dim retVal() As Result = Nothing
            Try
                retVal = JsonConvert.DeserializeAnonymousType(JsonString, retVal)
            Catch ex As Exception
                MessageBox.Show("Deserialize Failed -" & vbNewLine & ex.Message)
                retVal = Nothing
            End Try
            Return retVal
        End Function
    
        Function GetStringFromTxt(ByVal Txt As String) As String
            Dim retVal As String = ""
            Try
                Dim request As WebRequest = WebRequest.Create(Txt)
                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.Replace(vbTab, "")
        End Function
    
        Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles BtnGetRaw.Click
            Dim sb As New System.Text.StringBuilder("http://autocomplete.wunderground.com/aq?query=")
            sb.Append(TXTSrch.Text)
            sb.Append("&h=1")
            TXTRAW.Text = GetStringFromTxt(sb.ToString)
        End Sub
    
        Private Sub BtnConvert_Click(sender As System.Object, e As System.EventArgs) Handles BtnConvert.Click
            Dim sb As New System.Text.StringBuilder("http://autocomplete.wunderground.com/aq?query=")
            sb.Append(TXTSrch.Text)
            sb.Append("&h=1")
            JsonString = GetStringFromTxt(sb.ToString)
            Dim Matches() As Result
            Matches = GetResultsFromJSONString(JsonString)
            If Not Matches Is Nothing Then
                MessageBox.Show(Matches.Count.ToString)
            End If
        End Sub
    End Class
    Partial Public Class ACResults
        Public Shared Property RESULTS As ResultCollection
    End Class
    
    Partial Public Class ResultCollection
        Inherits ObjectModel.Collection(Of Result)
    End Class
    
    Partial Public Class Result
        Public Overridable Property name As String
        Public Overridable Property type As String
        Public Overridable Property c As String
        Public Overridable Property zmw As String
        Public Overridable Property tz As String
        Public Overridable Property tzs As String
        Public Overridable Property l As String
        Public Overridable Property ll As String
        Public Overridable Property lat As String
        Public Overridable Property lon As String
    End Class
    

    I have tried every combination I can think of, but the Deserialize fails. Please run this, put in a city name, part of zipcode, whatever, and click on "GetRAW" to see the raw data, then "Convert" to see the exception. I removed the TABs in the raw data, which didn't help. I used Xamasoft, VS2015, and Reed Kimble's MakePOCO on the raw data to get the Class Structure, didn't help.

    Exception:

    Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'WUAutoComplete.Result[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
    To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
    Path 'RESULTS', line 1, position 12.


    RAW data for one zipcode:

    { "RESULTS": [
    {
    "name": "93011 - Camarillo, CA", 
    "type": "city", 
    "c": "US",
    "zmw": "93011.1.99999",
    "tz": "America/Los_Angeles",
    "tzs": "PDT",
    "l": "/q/zmw:93011.1.99999",
    "ll": "34.220001 -119.040001",
    "lat": "34.220001",
    "lon": "-119.040001"
    }
    ]
    }

    Looks like an array with one member to me. 

    Thursday, March 16, 2017 9:14 PM

Answers

  • Devon,

    I created a text file and put it on my desktop:

    { "RESULTS": [
    {
    "name": "93011 - Camarillo, CA", 
    "type": "city", 
    "c": "US",
    "zmw": "93011.1.99999",
    "tz": "America/Los_Angeles",
    "tzs": "PDT",
    "l": "/q/zmw:93011.1.99999",
    "ll": "34.220001 -119.040001",
    "lat": "34.220001",
    "lon": "-119.040001"
    }
    ]
    }

    I then used the following code:

    Option Strict On Option Explicit On Option Infer Off Imports Newtonsoft.Json Public Class Form1 Private Sub Form1_Load(sender As System.Object, _ e As System.EventArgs) _ Handles MyBase.Load Dim desktop As String = _ Environment.GetFolderPath(Environment.SpecialFolder.Desktop) Dim filePath As String = _ IO.Path.Combine(desktop, "Devon_JSON.txt") If IO.File.Exists(filePath) Then Dim jText As String = IO.File.ReadAllText(filePath) If Not String.IsNullOrWhiteSpace(jText) Then Dim devonObject As Devon = _ JsonConvert.DeserializeObject(Of Devon)(jText) Stop End If End If End Sub End Class Public Class RESULT Public Property name As String Public Property type As String Public Property c As String Public Property zmw As String Public Property tz As String Public Property tzs As String Public Property l As String Public Property ll As String Public Property lat As String Public Property lon As String End Class Public Class Devon Public Property RESULTS As RESULT() End Class


    Here's the result:

    When it comes to JSON, get the strings. Put that in a hidden class if need be but get the strings as strings - then - do whatever you want in the Public class (like cast to double for Lat/Lon, etc.).

    I hope that helps. :)


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    • Marked as answer by Devon_Nullman Friday, March 17, 2017 12:27 AM
    Thursday, March 16, 2017 10:22 PM
  • RetVal should not be an array - the json result value is one object containing an array of results.

        Function GetResultsFromJSONString(JSON As String) As Result
            Dim retVal As Result = Nothing
            Try
                retVal = JsonConvert.DeserializeAnonymousType(JsonString, retVal)
            Catch ex As Exception
                MessageBox.Show("Deserialize Failed -" & vbNewLine & ex.Message)
                retVal = Nothing
            End Try
            Return retVal
        End Function

    I pushed a change out to my JsonCore library on OneDrive as I've recently been working on the code again but hadn't updated the online copy.  There were some bugs in the ToString output which have been corrected, along with a few other minor changes.  I think the T4 template may be broken in this copy... I was working on fixing/improving the json objects for direct manipulation so I haven't gotten around to looking at the T4 template yet.  The fixes and changes probably don't affect the POCO classes you've already generated.

    In my practical usage, I've found that the POCO classes aren't worth the effort to generate and its generally easier to use the json classes directly, and just reference properties by name.  I read the output of the request above to a listbox:

    Option Strict On
    
    Imports System.Net
    Imports System.IO
    Imports JsonCore
    
    Public Class Form1
        Private jsonResponse As JsonObject
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim request As WebRequest = WebRequest.Create("http://autocomplete.wunderground.com/aq?query=17820&h1")
            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()
                        jsonResponse = JsonParser.Parse(responseFromServer).First.AsObject
                    End Using
                End Using
            End Using
    
            Dim resultArray As JsonArray = jsonResponse.Property("RESULTS").AsArray
            For Each resultObject As JsonObject In resultArray
                ListBox1.Items.Add("RESULT:")
                For Each propertyname In resultObject.PropertyNames
                    ListBox1.Items.Add($"{propertyname} = {resultObject.Property(propertyname)}")
                Next
            Next
        End Sub
    End Class


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

    • Marked as answer by Devon_Nullman Friday, March 17, 2017 12:27 AM
    Thursday, March 16, 2017 11:01 PM
    Moderator

All replies

  • Devon,

    The deserialize method is generic. Did you look at what I posted the other day in the other thread by AWL?


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Thursday, March 16, 2017 9:28 PM
  • Devon,

    I created a text file and put it on my desktop:

    { "RESULTS": [
    {
    "name": "93011 - Camarillo, CA", 
    "type": "city", 
    "c": "US",
    "zmw": "93011.1.99999",
    "tz": "America/Los_Angeles",
    "tzs": "PDT",
    "l": "/q/zmw:93011.1.99999",
    "ll": "34.220001 -119.040001",
    "lat": "34.220001",
    "lon": "-119.040001"
    }
    ]
    }

    I then used the following code:

    Option Strict On Option Explicit On Option Infer Off Imports Newtonsoft.Json Public Class Form1 Private Sub Form1_Load(sender As System.Object, _ e As System.EventArgs) _ Handles MyBase.Load Dim desktop As String = _ Environment.GetFolderPath(Environment.SpecialFolder.Desktop) Dim filePath As String = _ IO.Path.Combine(desktop, "Devon_JSON.txt") If IO.File.Exists(filePath) Then Dim jText As String = IO.File.ReadAllText(filePath) If Not String.IsNullOrWhiteSpace(jText) Then Dim devonObject As Devon = _ JsonConvert.DeserializeObject(Of Devon)(jText) Stop End If End If End Sub End Class Public Class RESULT Public Property name As String Public Property type As String Public Property c As String Public Property zmw As String Public Property tz As String Public Property tzs As String Public Property l As String Public Property ll As String Public Property lat As String Public Property lon As String End Class Public Class Devon Public Property RESULTS As RESULT() End Class


    Here's the result:

    When it comes to JSON, get the strings. Put that in a hidden class if need be but get the strings as strings - then - do whatever you want in the Public class (like cast to double for Lat/Lon, etc.).

    I hope that helps. :)


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    • Marked as answer by Devon_Nullman Friday, March 17, 2017 12:27 AM
    Thursday, March 16, 2017 10:22 PM
  • RetVal should not be an array - the json result value is one object containing an array of results.

        Function GetResultsFromJSONString(JSON As String) As Result
            Dim retVal As Result = Nothing
            Try
                retVal = JsonConvert.DeserializeAnonymousType(JsonString, retVal)
            Catch ex As Exception
                MessageBox.Show("Deserialize Failed -" & vbNewLine & ex.Message)
                retVal = Nothing
            End Try
            Return retVal
        End Function

    I pushed a change out to my JsonCore library on OneDrive as I've recently been working on the code again but hadn't updated the online copy.  There were some bugs in the ToString output which have been corrected, along with a few other minor changes.  I think the T4 template may be broken in this copy... I was working on fixing/improving the json objects for direct manipulation so I haven't gotten around to looking at the T4 template yet.  The fixes and changes probably don't affect the POCO classes you've already generated.

    In my practical usage, I've found that the POCO classes aren't worth the effort to generate and its generally easier to use the json classes directly, and just reference properties by name.  I read the output of the request above to a listbox:

    Option Strict On
    
    Imports System.Net
    Imports System.IO
    Imports JsonCore
    
    Public Class Form1
        Private jsonResponse As JsonObject
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim request As WebRequest = WebRequest.Create("http://autocomplete.wunderground.com/aq?query=17820&h1")
            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()
                        jsonResponse = JsonParser.Parse(responseFromServer).First.AsObject
                    End Using
                End Using
            End Using
    
            Dim resultArray As JsonArray = jsonResponse.Property("RESULTS").AsArray
            For Each resultObject As JsonObject In resultArray
                ListBox1.Items.Add("RESULT:")
                For Each propertyname In resultObject.PropertyNames
                    ListBox1.Items.Add($"{propertyname} = {resultObject.Property(propertyname)}")
                Next
            Next
        End Sub
    End Class


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

    • Marked as answer by Devon_Nullman Friday, March 17, 2017 12:27 AM
    Thursday, March 16, 2017 11:01 PM
    Moderator
  • Thank You Frank and Reed, that solved the issue. Not sure why I have this mental block with JSON (do not answer that hypothetical question please)

    Friday, March 17, 2017 12:46 AM
  • Thank You Frank and Reed, that solved the issue. Not sure why I have this mental block with JSON (do not answer that hypothetical question please)

    With NewtonSoft, the DeserializeObject method is generic and the sample shows it:

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


    "A problem well stated is a problem half solved.” - Charles F. Kettering

    Friday, March 17, 2017 11:47 AM