vbdotnet Deserialize json to a class - json

I am getting the below exception when deserializing a simple json text to a class. please help.
"Exception thrown: 'Newtonsoft.Json.JsonReaderException' in Newtonsoft.Json.dll
An unhandled exception of type 'Newtonsoft.Json.JsonReaderException' occurred in Newtonsoft.Json.dll
Unexpected character encountered while parsing value: [. Path 'Array1', line 4, position 14."
Below is the simple json text
{
"Name1": "Value1",
"Name2": "Value2",
"Array1":["a", "b", "c", "d"]
}
and below is the Class/Model
Public Class Test
Public Property Name1 As String
Public Property Name2 As String
Public Property Array1() As String
End Class
and below is the main module
Imports System
Imports System.IO
Imports System.Collections.Generic
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Serialization
Imports Newtonsoft.Json.Converters
Imports Newtonsoft.Json.Linq
Module Module1
Sub Main()
'Here I am reading all text from the json file to "strJsonText" and then the below line throws 'exception
Dim classDocs = JsonConvert.DeserializeObject(strJsonText, GetType(Test))
End Sub
End Module

Visual Studio has a cool feature called Paste JSON as Classes that can be found under Edit > Paste Special > Paste JSON as Classes. Using this feature produces the same results you have, I wanted to share this with you because it is a nice feature to know about nonetheless.
With regards to your actual problem, the issue is where the parenthesis are in your array declaration. In Visual Basic .NET, the parenthesis are implicit in property definitions. That means that these two property definitions are functionally equivalent:
Public Property Name1() As String
' or
Public Property Name1 As String
This gets a bit confusing because an array variable can be declared as such:
Dim Array1() As String
But when you try to convert this to a property, you have to move the parenthesis to the end to compensate for the parenthesis in property definitions.
What I would suggest doing is changing your array definition to an IEnumerable instead and then explicitly cast it to an array if you need to. In other words, something like this:
Public Class Test
Public Property Name1 As String
Public Property Name2 As String
Public Property Array1 As IEnumerable(Of String)
Public Shared Function ParseJson(literal As String)
Return JsonConvert.DeserializeObject(Of Test)(literal)
End Function
End Class
Example: https://dotnetfiddle.net/DVgpI3

You are declaring this property wrong, it is actually a String type not an array, and it has an empty parameter section.
Public Property Array1() As String
It should be an array of strings
Public Property Array1 As String()
Furthermore, you should use the generic version of DeserializeObject
Dim classDocs = JsonConvert.DeserializeObject(Of Test)(strJsonText)
dotnetfiddle

You need to add Test class in your Json so you can deserialize
'Test': [
{
'Name1': 'Value1',
'Name2': 'Value2',
'Array1': [
'a',
'b',
'c',
'd'
]
]
}

Related

Json conversion to double and bind to WPF nummericupdown fails

Currently i get an error on a binding. The situation is that i write my settings to a JSON file. When the app opens again the JSON file is read and used throughout the application. Now there's a strange thing: When i bind a double value to the value of a nummericupdown than i get an error: type 'JValue' to type 'System.Nullable1[System.Double]'for 'en-US' however this error doesn't occur when i recreate the JSON list and file. (simply said when i delete the file and restart the app it will create a new instance of a class en write it to disk)
Property in class:
Public Property SomeValue As Double
Write/Reader JSON:
'Write
Using _file As StreamWriter = New StreamWriter(SettingFilePath)
Dim serializer As New JsonSerializer()
serializer.Formatting = Formatting.Indented
serializer.Serialize(_file, Me)
End Using
'Read
Return JsonConvert.DeserializeObject(Of Settings)(File.ReadAllText(settingsfile))
JSON string:
"SomeValue": 1.0,
Binding in XAML:
<Controls:NumericUpDown
Width="200"
HorizontalAlignment="Center"
Maximum="5"
Minimum="1"
NumericInputMode="All"
Speedup="false"
Value="{Binding SomeValue}" />
Please note that i use the Mathapps Metro nummericupdown control version 1.6.5
Newtonsoft version 10.0.0.1 (Cannot update due to dependencies)
Edit:
As asked i digged deeper and now know where it starts, but don't know yet how to resolve it. Is start with my class for example:
Public class Hello
Dim a as Object
Dim b as EnumTypeOfObjectIn_A
Dim SomeOtherStuff as String
End class
Now when i DeserializeObject de file to the Class Hello then variable a becomes a object of type JObject and this is why alot of logica afterwords goes wrong. When i create the object in code everything goes well because the TypeOf object matches the one i put in. So is there a work arround for the Deserializer to convert the object to the one that is indicated in variable b ?
Found the solution i was looking for. The Newtonsoft JSON contains the JsonSerializerSettings class that helps the de/serialization process. For me it is important to add the Type of object when serializing so that's exactly what TypeNameHandling and TypeNameAssemblyFormatHandling is for in the Newtonsoft JSON assembly
I ended up with this code:
Public Class Hello
Public Property A As Object
Dim settingsfile As String = "C:\jsontest.json"
Public Sub Save()
Using _file As StreamWriter = New StreamWriter(settingsfile)
_file.Write(JsonConvert.SerializeObject(Me, Formatting.Indented, New JsonSerializerSettings() With {
.TypeNameHandling = TypeNameHandling.Objects,
.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
}))
End Using
End Sub
Public Function Load() As Hello
Return JsonConvert.DeserializeObject(Of Hello)(File.ReadAllText(settingsfile), New JsonSerializerSettings() With {.TypeNameHandling = TypeNameHandling.Objects})
End Function
End Class
Public Class Person
Public Property Name As String
Public Property Age As Integer
Sub New()
Me.Name = "John"
Me.Age = 130
End Sub
End Class
Producing this JSON output, note the $Type
{
"$type": "MyNamespace.Hello, MyNamespace",
"A": {
"$type": "MyNamespace.Person, MyNamespace",
"Name": "John",
"Age": 130
}
}

How to deserialize this JSON with VB.NET

I have this JSON data:
{"asks":[["0.26039995",19.91610429],["0.26063345",3070.562292]],"bids":[["0.26000017",30381.45513902],["0.26000000",8299.1410574]],"isFrozen":"0","seq":50663190}
I wrote this code:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim wc As New WebClient
Dim sURL As String = "https://poloniex.com/public?command=returnOrderBook&currencyPair=USDT_STR&depth=" & 2
Dim res As String = wc.DownloadString(New Uri(sURL))
Dim m As IEnumerable(Of Rootobject) = JsonConvert.DeserializeObject(Of IEnumerable(Of Rootobject))(res)
End Sub
Public Class Rootobject
Public Property asks As asksDef()
Public Property bids As bidsDef()
Public Property isFrozen As String
Public Property seq As Integer
End Class
Public Class asksDef
Public Property priceAsk As String
Public Property quantAsk As Integer
End Class
Public Class bidsDef
Public Property priceBid As String
Public Property quantBid As Integer
End Class
I've pasted the JSON class with VB paste special.
The question is: how to access to every ask, every bid and the isFrozen and seq values.
I got an error on this line:
Dim m As IEnumerable(Of Rootobject) = JsonConvert.DeserializeObject(Of IEnumerable(Of Rootobject))(res)
The error message I got is:
An unhandled exception of type
'Newtonsoft.Json.JsonSerializationException' occurred in
Newtonsoft.Json.dll
Additional information: Cannot deserialize the current JSON object
(e.g. {"name":"value"}) into type
'System.Collections.Generic.IEnumerable`1[poloniexAPI.Rootobject]'
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) 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 'asks', line 1, position 8.
I'm stuck again this time with this JSON model. How to proceed with this?
{"BTC_BCN":{"id":7,"last":"0.00000042","lowestAsk":"0.00000043","highestBid":"0.00000042","percentChange":"0.00000000","baseVolume":"179.56364789","quoteVolume":"436786711.33832335","isFrozen":"0","high24hr":"0.00000043","low24hr":"0.00000039"},"BTC_BELA":{"id":8,"last":"0.00002091","lowestAsk":"0.00002097","highestBid":"0.00002091","percentChange":"-0.10831556","baseVolume":"12.57891843","quoteVolume":"579476.06165462","isFrozen":"0","high24hr":"0.00002345","low24hr":"0.00002088"}}
The root cause of this specific error is that your JSON represents a single object (which contains some arrays and other info) but you are trying to deserialize it as if the whole thing were enumerable. However, fixing this will not solve the whole problem; there are a couple of other issues as well.
This JSON is a little odd because it uses an array to group together each price and quantity pair for the bids and asks, whereas an object seems like it would be more appropriate (and more easily consumable). Since Json.Net does not have a facility to automatically map an array into class properties by index, you will need to use a custom JsonConverter to deserialize this data properly. Also, the prices are represented as strings for some reason where they should be decimals like the quantity. This can be handled in the converter as well.
Before we get to the converter, let's fix your class definitions. Since the bids and asks use the same structure, I would recommend defining a common class for that. Both price and quantity properties should be declared as Decimal:
Public Class PriceQuantityPair
Public Property price As Decimal
Public Property quantity As Decimal
End Class
Then define your root class like this:
Class RootObject
Public Property asks As List(Of PriceQuantityPair)
Public Property bids As List(Of PriceQuantityPair)
Public Property isFrozen As String
Public Property seq As Integer
End Class
Here is the code for the converter, which will translate the array structure for each pair into a PriceQuantityPair instance:
Class PriceQuantityPairConverter
Inherits JsonConverter
Public Overrides Function CanConvert(objectType As Type) As Boolean
Return objectType Is GetType(PriceQuantityPair)
End Function
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim ja As JArray = JArray.Load(reader)
Dim pair As PriceQuantityPair = New PriceQuantityPair()
pair.price = ja(0).ToObject(Of Decimal)()
pair.quantity = ja(1).ToObject(Of Decimal)()
Return pair
End Function
Public Overrides ReadOnly Property CanWrite As Boolean
Get
Return False
End Get
End Property
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Throw New NotImplementedException()
End Sub
End Class
To use the converter, add a <JsonConverter> attribute to the PriceQuantityPair class like this:
<JsonConverter(GetType(PriceQuantityPairConverter))>
Public Class PriceQuantityPair
...
End Class
Finally, deserialize the JSON into the RootObject class like this:
Dim root As RootObject = JsonConvert.DeserializeObject(Of RootObject)(json)
Here is a demo: https://dotnetfiddle.net/dDHLtR
As far as I understand your question.
You can access the properties with the 'm' variable where you are storing the de serialized data.
Ex: m.isFrozen, m.seq, m.bids() & m.asks().

Deserialise JSON message of two types

I try to consume web service returning JSON messages. Each method returns relevant JSON message (typically an array) or JSON error message (not an array).
Examples of UserList and Error messages:
[
{
"id":1,
"login":"john1",
"full_name":"John Smith"
},
{
"id":2,
"login":"anne",
"full_name":"Anne Steward"
}
]
{
"success":false,
" message":"Unknown login"
}
Because code does not know which message to expect, I have created two classes where UserList inherits from BaseResponse (an error message class). I have used inheritance, as all other methods like GetCarList, etc. are suitable of returning an error message as well. So no need to declare error fields multiple times. That had to be smart - I will always get or UserList fields or error message fields in the same object.
Public Class BaseResponse
Public success As Boolean
Public message As String
End Class
Public Class UserListResponse
Inherits BaseResponse
Public id As Integer
Public login As String
Public full_name As String
End Class
The problem is, that UserListResponse JSON message is an array, while ErrorMessage JSON is not an array. So deserialising will work when JSON will be UserList but will not work when JSON will be an error message:
JsonConvert.DeserializeObject(Of List(Of UserListResponse))(ReceivedJSONMessage)
Any ideas how to tackle this in smart way?
If you are certain that both of your return classes are working for deserializing the json correctly you just need in essence: "Am I a list or a string?" to ask for one path or the other.
EDIT 8-30
Okay so I missed that you do not KNOW till after it was executed, my bad. You can do the logic inside of Sub Routine instead of a Function then that does the logic directly on examining a generic object passed instead. Similar to this.
Private Sub DetermineReturn(obj As Object)
If obj.GetType = GetType(String) Then
Console.WriteLine(CType(obj, String))
ElseIf obj.GetType = GetType(List(Of String)) Then
CType(obj, List(Of String)).ForEach(Sub(x) Console.WriteLine(x))
Else
Console.WriteLine("UnKnOwN!1!")
End If
End Sub
Sub Main()
Dim testError = "Oh Shoot Error!"
Dim GoodStuff = New List(Of String)({"I", "am", "a", "list"})
Console.WriteLine("FirstExample")
DetermineReturn(testError)
Console.WriteLine()
Console.WriteLine("SecondExample")
DetermineReturn(GoodStuff)
Console.WriteLine()
Console.WriteLine("ThirdExample")
DetermineReturn(1)
Console.ReadLine()
End Sub
Essentially You would just do the work of either transforming your JSON inside directly of the method 'DetermineReturn' instead of Console.WriteLines. I am interpreting the 'obj' passed in, using reflection to get it's type, and then it is safe to be cast as that type. So you would put the argument of your JSON return in the method like DetermineReturn((YourJSONResult)) and then inside the method do whatever transforms you need on the object.

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`

I know there's already a bunch of these questions on SO but none of those answers is working for me. I've been on it all day now and I'm just fried so it's an easy win for the JSON people out there.
The error is
An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
Additional information: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[FrozenSmoke.WorkflowDescription]' 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 'Workflows', line 1, position 13.
The code is (VB.NET)
Dim z As New List(Of WorkflowDescription)
z = Newtonsoft.Json.JsonConvert.DeserializeObject(Of List(Of WorkflowDescription))(ResponseStatus.Content)
Here is the JSON I'm getting back from the server and is contained in ResponseStatus.Content
{"Workflows":[{"GUID":"594d2946-7bee-49b3-b8bf-e5ee6715a188","Name":"ProcessElectronicContribution"},{"GUID":"fe368a11-2b79-41f9-bee9-edb031612365","Name":"AddActivist"},{"GUID":"4c552492-0014-439d-952b-aeb320e6d218","Name":"AddPartialActivist"}]}
Here is the class
Public Class WorkflowDescription
Public Property Name As String = ""
Public Property GUID As String = ""
End Class
Your json is not simply an array/list but an object container which contains a Workflows property which is a list/array.
{"Workflows":[{"GUID": ....
Workflows is the List/Array but note that it is inside a brace. That is the "outer container".
Public Class WorkFlowContainer
Public Property WorkFlows As List(Of WorkflowItem)
End Class
Public Class WorkflowItem
Public Property Name As String
Public Property GUID As String
End Class
The WorkFlows element in the json will map to the property of the same name. You can get rid of the container after you deserialize:
' ultimate destination
Private myWorkFlows As List(Of WorkflowItem)
...
' elsewhere
Dim jstr = ... ` from whereever
' deserialize to the container
Dim wf = JsonConvert.DeserializeObject(Of WorkFlowContainer)(jstr)
' extract the list
myWorkFlows = wf.WorkFlows
You can ignore the WorkFlowContainer with an extra step in deseriazlizing:
Dim jstr = ... ` from whereever
Dim jopbj - JObject.Parse(jstr)
myWorkFlows = JsonConvert.DeserializeObject(Of List(Of WorkFlows))(jobj("Workflows"))
This way you dont have to define the extra class and dont have to use wf. to get at the data.
You probably know that most arrays can be deserialized as either a List or array. To define Workflows as a property array:
Public Property WorkFlows As WorkflowItem()

ArgumentOutOfRange exception when deserializing to a DataSet with Json.Net

I have the following JSON string in a variable called strJSON.
{
"results":[
{
"templateName":"HUD Section 8",
"userID":"2",
"mobileObjectId":"4582",
"source":"M",
"inspectionType":"A",
"notes":"Window in bedroom needs repair.",
"agencyID":"",
"requestDate":"2014-05-09 00:00:00",
"agencyName":"",
"inspectionTimeBegun":"2014-05-09 14:00:17",
"inspectionDate":"2014-05-09 14:30:00",
"inspectionID":135,
"inspectionTimeComplete":"2014-05-09 14:29:25",
"summaryDecision":"F",
"createdAt":"2014-05-09T18:29:35.050Z",
"updatedAt":"2014-05-09T18:29:35.050Z",
"objectId":"1FgtD6WT8Y",
"ACL":{
"*":{
"read":true
},
"cryZoU5gXJ":{
"write":true,
"read":true
}
}
}
]
}
When I call the following line of code...
ds = Newtonsoft.Json.JsonConvert.DeserializeObject(Of DataSet)(strJSON)
I get an exception with the message stating "Specified argument was out of the range of valid values"
The JSON string is created with the following REST API call to Parse.com.
strJSON = http.QuickGetStr(strURL)
I am using this elsewhere with success albeit with simpler Parse classes but I have gone through this JSON string carefully and can't see anything wrong.
Any ideas on what might be causing this error?
In order for Json.Net to deserialize into a DataSet, the JSON must be in a specific format, as described in this answer. Your JSON is close, but the problem is the ACL object. The DataTableConverter that Json.Net 5.0 uses expects all of the columns in the table to be simple data types or it will throw an ArgumentOutOfRangeException (source). Json.Net 6.0 supports nested data tables and arrays in addition to simple types, but your ACL data still does not meet the required format that would allow it to be deserialized correctly to a DataSet. You have a few different options for dealing with this:
Change the JSON
If you control the format of the JSON (i.e. it is not from a third party) you can change it such that Json.Net 6.0 will be able to deserialize it to a DataSet. Here is what it would need to look like for that to work:
{
"results": [
{
"templateName": "HUD Section 8",
"userID": "2",
"mobileObjectId": "4582",
"source": "M",
"inspectionType": "A",
"notes": "Window in bedroom needs repair.",
"agencyID": "",
"requestDate": "2014-05-09 00:00:00",
"agencyName": "",
"inspectionTimeBegun": "2014-05-09 14:00:17",
"inspectionDate": "2014-05-09 14:30:00",
"inspectionID": 135,
"inspectionTimeComplete": "2014-05-09 14:29:25",
"summaryDecision": "F",
"createdAt": "2014-05-09T18:29:35.050Z",
"updatedAt": "2014-05-09T18:29:35.050Z",
"objectId": "1FgtD6WT8Y",
"ACL": [
{
"user": "*",
"read": true,
"write": false
},
{
"user": "cryZoU5gXJ",
"read": true,
"write": true
}
]
}
]
}
With this format, the ACL column of the results table will contain a nested DataTable with the individual ACL rows, each row having three columns, user, read and write.
Deserialize to strongly-typed classes
Instead of deserializing into a DataSet, you could deserialize into a set of strongly-typed classes. The advantage to this approach is that everything is in an easily usable form. The disadvantage is that you need to know what is in the JSON before you can create the classes.
You can use third-party tools like json2csharp.com to help generate the classes from a sample of the JSON, as was suggested in another answer (now deleted), but note that this is not foolproof (and it doesn't do VB). Sometimes you will need to intervene and edit the classes manually. For example, if generate classes from the JSON in your question, you'll notice that it creates a fixed class for each ACL instance. This will not work unless your set of ACLs always has exactly two items, one called Everyone and the other CryZoU5gXJ. I think it is much more likely that the set of ACLs will be variable, so it makes sense to use a Dictionary for these. Here are the classes I would propose:
Class RootObject
Public Property results As List(Of Result)
End Class
Class Result
Public Property templateName As String
Public Property userID As String
Public Property mobileObjectId As String
Public Property source As String
Public Property inspectionType As String
Public Property notes As String
Public Property agencyID As String
Public Property requestDate As String
Public Property agencyName As String
Public Property inspectionTimeBegun As String
Public Property inspectionDate As String
Public Property inspectionID As Integer
Public Property inspectionTimeComplete As String
Public Property summaryDecision As String
Public Property createdAt As String
Public Property updatedAt As String
Public Property objectId As String
Public Property ACL As Dictionary(Of String, ACL)
End Class
Class ACL
Public Property read As Boolean
Public Property write As Boolean
End Class
With this class structure in place, you can deserialize like this:
Dim root As RootObject = JsonConvert.DeserializeObject(Of RootObject)(strJSON)
For the ACLs, the key for each dictionary entry will be the user ID (or * as you have in your example). If you don't actually care about the ACLs, you can simply omit the ACL property from the Result class. By default Json.Net will skip properties that exist in the JSON but do not exist in the class.
Use the LINQ-to-JSON API to parse the JSON
With Json.Net there is always more than one way to skin the cat. Json.Net's LINQ-to-JSON API really shines when the JSON you are parsing is highly variable and/or you don't want to create classes for receiving the data. You can deserialize any valid JSON to a hierarchy of JToken objects and then pick them apart however you need. For example, if you just needed a few select pieces of information from each result, you could do this:
Dim token As JToken = JToken.Parse(json)
For Each result As JObject In token("results").Children(Of JObject)()
Console.WriteLine("userID: " + result("userID").ToString())
Console.WriteLine("templateName: " + result("templateName").ToString())
Console.WriteLine("inspectionID: " + result("inspectionID").ToString())
Console.WriteLine("inspectionType: " + result("inspectionType").ToString())
Console.WriteLine("inspectionDate: " + result("inspectionDate").ToString())
Console.WriteLine("summaryDecision: " + result("summaryDecision").ToString())
Console.WriteLine("notes: " + result("notes").ToString())
Next
You could use this same approach to manually build a DataSet from the JSON. Here is a generic function that will deserialize JSON into a DataSet but ignore any complex objects (e.g. the ACLs) instead of throwing an exception:
Function DeserializeToDataSet(json As String) As DataSet
Dim root As JObject = JObject.Parse(json)
Dim ds As DataSet = New DataSet()
For Each prop As JProperty In root.Properties
If prop.Value.Type = JTokenType.Array Then
Dim dt As DataTable = ds.Tables.Add(prop.Name)
For Each row As JObject In prop.Value.Children(Of JObject)()
Dim dr As DataRow = dt.NewRow
For Each col As JProperty In row.Properties
Dim colType As Type = GetColumnType(col.Value.Type)
If Not colType Is Nothing Then
Dim dc As DataColumn = dt.Columns(col.Name)
If dc Is Nothing Then
dc = dt.Columns.Add(col.Name, colType)
End If
dr(col.Name) = col.Value.ToObject(colType)
End If
Next
dt.Rows.Add(dr)
Next
End If
Next
Return ds
End Function
Function GetColumnType(tokenType As JTokenType) As Type
If tokenType = JTokenType.String Then Return GetType(String)
If tokenType = JTokenType.Integer Then Return GetType(Integer)
If tokenType = JTokenType.Date Then Return GetType(DateTime)
If tokenType = JTokenType.Boolean Then Return GetType(Boolean)
If tokenType = JTokenType.Float Then Return GetType(Double)
Return Nothing
End Function
Of course if you need the ACLs, you'll need to customize this method to get that data into a form that is consumable by your code. I'll leave that part to you.
Json.Net will only parse directly into a DataSet if it conforms to a certain standard. See this answer for the layout it needs.
However, you could deserialize to an XML document and use the DataSet object's ReadXml method load it for you. See this question for details on how to do this.
(HT to Brian Rogers for dataset structure details)