Class to JSON VB.Net - json

So I have a class which I am serializing to Json. All goes well, until this nested class, which gives me an System.NullReferenceException = {"Object reference not set to an instance of an object."}. When writing the code, intelisense recognizes the nested class, but obviously I'm missing a declaration somewhere.
Root class:
Public Class RootObject
Private _metadata As List(Of Metadata)
Public Property metadata() As List(Of Metadata)
Get
Return _metadata
End Get
Set(ByVal value As List(Of Metadata))
_metadata = value
End Set
End Property
Private _test_gl As List(Of TestGl)
Public Property test_gl() As List(Of TestGl)
Get
Return _test_gl
End Get
Set(ByVal value As List(Of TestGl))
_test_gl = value
End Set
End Property
End Class
Here is the TestGl class definition:
Public Class TestGl
Private _ref_key_3 As String
<JsonProperty("ref-key-3")> _
Public Property ref_key_3() As String
Get
Return _ref_key_3
End Get
Set(ByVal value As String)
_ref_key_3 = value
End Set
End Property
Private _currency_amount As CurrencyAmount
<JsonProperty("currency-amount")> _
Public Property currency_amount() As CurrencyAmount
Get
Return _currency_amount
End Get
Set(ByVal value As CurrencyAmount)
_currency_amount = value
End Set
End Property
End Class
And finally the CurrencyAmount class:
Public Class CurrencyAmount
Private _currency As String
<JsonProperty("currency")> _
Public Property currency() As String
Get
Return _currency
End Get
Set(ByVal value As String)
_currency = value
End Set
End Property
Private _amount As String
<JsonProperty("amount")> _
Public Property amount() As String
Get
Return _amount
End Get
Set(ByVal value As String)
_amount = value
End Set
End Property
End Class
Here follow the code of filling up the object with data:
Dim Root As RootObject
Dim Meta_Data As New List(Of Metadata)()
Dim Test_Gl As New List(Of TestGl)()
Root = New RootObject
Root.metadata = New List(Of Metadata)()
Root.test_gl = New List(Of TestGl)
Meta_Data = Root.metadata
Test_Gl = Root.test_gl
And here I assign values to it:
Test_Gl.Add(New AccountGl)
Test_Gl(ItemNo).ref_key_3 = "test"
Test_Gl(ItemNo).currency_amount.currency = "EUR"
Test_Gl(ItemNo).currency_amount.amount = "100"
The line where currency_amount.currency gets assigned, it goes wrong and I'm looking at it for several hours already. I don't see it.
Any input would be highly appreciated.
The properties are written in full as I need to work on this project in VS2008.

I suspect somewhere you need to initialize _currency_amount to a new instance of CurrencyAmount I don't see new CurrencyAmount anywhere.
I suspect that you don't really want to allow the currency_amount property to be set, otherwise you should have set it in your sample assignment code at the bottom. If this is the case, then you probably shouldn't even have a Set member defined for TestGl (it should be ReadOnly, which affects only currency_amount and not _currency_amount.currency). Instead you should create a default instance of CurrencyAmount and assign it to that field during the construction of TestGl. That could be as simple as changing your declaration of _currency_amount to:
Private _currency_amount As New CurrencyAmount
Alternatively, and this may be the solution you need to use with a JSON serializable object, you keep the Set member definition, and just add a line to your test code to initialize currency_amount before using it:
Test_Gl(ItemNo).currency_amount = new CurrencyAmount

Related

JsonConvert.DeserializeObject knowing class name (string)

I've a vb.net class that includes some serializable sub-classes
Public Class Model
<Serializable>
Public class foo
Public Property id as Integer
Public Property name as String
End Class
<Serializable>
Public Class bar
Public Property grading as Integer
Public Property minWage as Decimal
Public Property maxWage as Decimal
End Class
End Class
Now I receive from a web service, an object (as json formated string) and its class name (as string) and I'd like to Deserialize it as my object.
I could do, as it is actually working, a
Imports Newtonsoft.Json
Public Shared Function Deserialize(ByVal Json As String, ByVal name As String) As Object
Select Case name
Case "foo"
Return JsonConvert.DeserializeObject(of Model.foo)(Json)
Case "bar"
Return JsonConvert.DeserializeObject(of Model.bar)(Json)
End Select
End Function
But as you can imagine, I've a ton of classes, not just 2, so I tried to use Reflection
Imports System.Reflection
Imports Newtonsoft.Json
Public Shared Function Deserialize(ByVal Json As String, ByVal name As String) As Object
Dim assembly As Assembly = Assembly.GetAssembly(GetType(Model))
Dim type As Type = assembly.[GetType]("Model." & name) ' Why is this always Nothing?
Dim objInstance As Object = Activator.CreateInstance(type)
' objInstance = JsonConvert.DeserializeObject(Of ???)(Json)
Return objInstance
End Function
But on one hand the type variable is always Nothing, and on the other hand, I don't know what to put instead of the comment.
Can you please advise ?
You need to add the Namespace where the Model class is defined.
A nested class is separated by a +, so you have to add that to the full name of the Class object you want to create.
The composed string is then "[Namespace].[ClassName]+[NestedClassName]"
You don't need to create the instance, the Json Serializer will do that for you. Pass the Type of the class to the JsonConvert.DeserializeObject() method that accepts a Type as the second parameter.
Public Shared Function Deserialize(Json As String, className As String) As Object
Dim asm = Assembly.GetAssembly(GetType(Model))
Dim t As Type = asm.GetType($"{asm.GetName().Name}.Model+{className}")
Return JsonConvert.DeserializeObject(Json, t)
End Function
How you're going to use this, it's up to you :)
More generic:
Public Shared Function Deserialize(Of T)(Json As String, className As String) As Object
Dim asm = Assembly.GetAssembly(GetType(T))
Dim t1 As Type = asm.GetType($"{asm.GetName().Name}.{GetType(T).Name}+{className}")
Return JsonConvert.DeserializeObject(Json, t1)
End Function
Call as:
Dim deserialized = Deserialize(Of Model)(Json, "foo")
I've partially solve my problem, I can now crerate the right object, like so
Dim objType As Type = (From asm In AppDomain.CurrentDomain.GetAssemblies() From type In asm.GetTypes() Where type.IsClass AndAlso type.Name = name Select type).Single()
Dim objInstance As Object = Activator.CreateInstance(objType)
This is my final, working function, thanks to Jimi!
Public Shared Function Deserialize(ByVal Json As String, ByVal name As String) As Object
Try
Dim objType As Type = (From asm In AppDomain.CurrentDomain.GetAssemblies() From type In asm.GetTypes() Where type.IsClass AndAlso type.Name = name Select type).Single()
Return JsonConvert.DeserializeObject(Json, objtype)
Catch ex As Exception
Throw New Exception("Unknown class")
End Try
End Function

VB.NET Deserialize JSON - Cannot deserialize the current JSON object into type .Customer[]' because the type requires a JSON array

I am working on a system which will send off a POST or GET request to a web API in VB.NET.
I am using Newtonsoft.Json to do convert the returing JSON string into a VB object.
I am getting the following error when trying to deserialize the response.
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'ProjectName.Customer[]' 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 'customers.id', line 1, position 19.
The JSON string the API returns to me is:
{"customers":{"id":"CU0004FEY6D7HA","created_at":"2018-10-13T13:30:21.320Z","email":"test#test.com","given_name":"Joe","family_name":"Bloggs","company_name":null,"address_line1":"1 Street Name","address_line2":"","address_line3":null,"city":"London","region":null,"postal_code":"SW1A 1AA","country_code":"GB","language":"en","swedish_identity_number":null,"danish_identity_number":null,"phone_number":null,"metadata":{}}}
I have created a class for the object.
VB.NET isn't my strongest skill since I mainly work with PHP.
Can anyone offer an helpful suggestions?
Public Class CustomerWrapper
Public customers() As Customer
End Class
Public Class Metadata
Public Property id() As String
Get
Return m_id
End Get
Set(value As String)
m_id = value
End Set
End Property
Private m_id As String
End Class
Public Class Customer
Public Property id() As String
Get
Return m_id
End Get
Set(value As String)
m_id = value
End Set
End Property
Private m_id As String
Public Property created_at() As String
Get
Return m_created_at
End Get
Set(value As String)
m_created_at = value
End Set
End Property
Private m_created_at As String
Public Property email() As String
Get
Return m_email
End Get
Set(value As String)
m_email = value
End Set
End Property
Private m_email As String
Public Property given_name() As String
Get
Return m_given_name
End Get
Set(value As String)
m_given_name = value
End Set
End Property
Private m_given_name As String
Public Property family_name() As String
Get
Return m_family_name
End Get
Set(value As String)
m_family_name = value
End Set
End Property
Private m_family_name As String
Public Property address_line1() As String
Get
Return m_address_line1
End Get
Set(value As String)
m_address_line1 = value
End Set
End Property
Private m_address_line1 As String
Public Property address_line2() As String
Get
Return m_address_line2
End Get
Set(value As String)
m_address_line2 = value
End Set
End Property
Private m_address_line2 As String
Public Property address_line3() As String
Get
Return m_address_line3
End Get
Set(value As String)
m_address_line3 = value
End Set
End Property
Private m_address_line3 As String
Public Property city() As String
Get
Return m_city
End Get
Set(value As String)
m_city = value
End Set
End Property
Private m_city As String
Public Property region() As String
Get
Return m_region
End Get
Set(value As String)
m_region = value
End Set
End Property
Private m_region As String
Public Property postal_code() As String
Get
Return m_postal_code
End Get
Set(value As String)
m_postal_code = value
End Set
End Property
Private m_postal_code As String
Public Property country_code() As String
Get
Return m_country_code
End Get
Set(value As String)
m_country_code = value
End Set
End Property
Private m_country_code As String
Public Property language() As String
Get
Return m_language
End Get
Set(value As String)
m_language = value
End Set
End Property
Private m_language As String
Public Property phone_number() As String
Get
Return m_phone_number
End Get
Set(value As String)
m_phone_number = value
End Set
End Property
Private m_phone_number As String
Public metadata() As Metadata
End Class
The code calling Json is
Dim response = apiCall.CallApi()
'Dim customerId = customers("id")
Dim customerWrapper = JsonConvert.DeserializeObject(Of CustomerWrapper)(response)
Dim customers = customerWrapper.customers
You are getting this exception because you have omitted the Property keyword from a couple of the members in your classes, which changes the semantics of the parentheses.
For example, in your CustomerWrapper class, you have declared the customers member like this:
Public customers() As Customer
Since the declaration does not have the Property keyword, this means that customers is a field in the class and the parenthesis here mean that it is an array. This is actually the same as declaring the field with the parentheses after the data type:
Public customers As Customer() ' Field - array of Customer
Conversely, in a property declaration, the parentheses after the name mean something entirely different: here, they denote an empty parameter list. (A property is actually a type of method, so it can have parameters, though most properties do not. The parentheses after the property name are optional if the property does not have any parameters.)
Public Property customers() As Customer ' Property - single Customer
So, the bottom line is you are trying to deserialize a single customer object into a customers field that is declared as an array, resulting in the exception you see.
To fix, just add the Property keyword as shown above. You will also need to fix the metadata field in your Customer class; it has the same problem.
As an FYI, you can simplify your code a lot by getting rid of the backing fields. None of your properties have any logic in them, so you can omit the implementations and use auto-implemented properties instead (basically the compiler generates the backing fields and get/set boilerplate for you behind the scenes). I would also get rid of the optional parentheses on the property names while you're at it.
With these changes, your classes would look like this, which is much more readable IMO:
Public Class CustomerWrapper
Public Property customers As Customer
End Class
Public Class Metadata
Public Property id As String
End Class
Public Class Customer
Public Property id As String
Public Property created_at As String
Public Property email As String
Public Property given_name As String
Public Property family_name As String
Public Property address_line1 As String
Public Property address_line2 As String
Public Property address_line3 As String
Public Property city As String
Public Property region As String
Public Property postal_code As String
Public Property country_code As String
Public Property language As String
Public Property phone_number As String
Public Property metadata As Metadata
End Class
Fiddle: https://dotnetfiddle.net/vEnbnI

json key with array of values - how to parse

This is the json data I am trying to parse. (I did trim the imagedata down for example purposes)
{"imageData":["SUkqAORlAACGniG0JCHeSTV9icwWxF+N9AwzcsTDlLu+PeYCgeZXAP//","sfsdfsdyfhh2h43h8ysdfsdnvjknjfdsfdsf"]}
Any idea on how to parse it into a strongly typed class in .NET?
I am using the newtonsoft.json
I tried the following
Public Class DAFGImages
Public imageData As List(Of String)
End Class
Dim DAFGImages As List(Of DAFGImages) = Newtonsoft.Json.JsonConvert.DeserializeObject(json, GetType(List(Of DAFGImages)))
Your class already contains a List of string, so you dont need a list of DAFGImages too. I would change the class member to a property:
Public Class DAFGImages
Public Property imageData As List(Of String)
End Class
Then:
Dim jstr = ... from wherever
Dim myImgs = Newtonsoft.Json.JsonConvert.DeserializeObject(Of DAFGImages)(jstr)
myImgs.ImageData will contain the 2 elements.

ServiceStack.Text reading json results not working

I am just trying to figure out the best way to deserialize a json string returned from a 3rd party api call. I read ServiceStack is fast so want to try it out. No experience and here is what I have done:
Opened Visual Studio 2013
Created new project Windows Forms Application
Installed ServiceStack.Text (based on https://servicestack.net/download)
Added a button (btnView) and textbox (txtOutput)
Add code to btnView_Click event
Private Sub btnView_Click(sender As Object, e As EventArgs) Handles btnView.Click
Me.Cursor = Cursors.WaitCursor
Dim wp As New WebPost 'this allows to pass url and return results
wp.URL = "xxxx"
Dim sJSONRetVal As String = wp.Request(String.Empty, True)
'sJSONRetVal return values looks like the following:
'{"complaints":[{"feedback_type":"abuse","subject":"Sales Agent Position"},{"feedback_type":"abuse","subject":"Sales Agent Position"}],"message":"OK","code":0}
'ServiceStack.Text example
Dim t As SMTP_Complaints = ServiceStack.Text.JsonSerializer.DeserializeFromString(Of SMTP_Complaints)(sJSONRetVal)
'For Each xi As SMTP_Complaints In t
' txtOutput.Text &= xi.mail_from & vbCrLf
'Next
wp = Nothing
txtOutput.Text = t.ToString
Me.Cursor = Cursors.Default
End Sub
Public Class SMTP_Complaints
Dim _feedback_type As String = ""
Dim _subject As String = ""
Public Property feedback_type As String
Get
Return _feedback_type
End Get
Set(value As String)
_feedback_type = value
End Set
End Property
Public Property subject As String
Get
Return _subject
End Get
Set(value As String)
_subject = value
End Set
End Property
End Class
The above doesn't seem to get any data. how would I loop through the data returned and return the data from both instances? Just not sure how I need to set this up to read the json data and then be able to output.
Based on the returned JSON of:
{"complaints":[{"feedback_type":"abuse","subject":"Sales Agent Position"},{"feedback_type":"abuse","subject":"Sales Agent Position"}],"message":"OK","code":0}
You will need two DTOs to deserialise this result.
I have used auto implemented properties here to simplify the complexity of the code. If you use an older version of VB, you'll need to expand these out to include a backing field with get and set method.
Public Class SMTP_Complaint
Public Property feedback_type As String
Public Property subject As String
End Class
Public Class SMTP_ComplaintsResponse
Public Property complaints As SMTP_Complaint()
Public Property message As String
Public Property code As Integer
End Class
You need the SMTP_ComplaintsResponse class because your complaints are wrapped in your JSON response.
Then to deserialise the response:
Dim response = JsonSerializer.DeserializeFromString(Of SMTP_ComplaintsResponse)(sJSONRetVal)
And your complaints are then accessible:
For Each complaint As var In response.complaints
Console.WriteLine("Type: {0}, Subject {1}", complaint.feedback_type, complaint.subject)
Next

TreeView, Linq-To-SQL recursive data population

I have an IEnumerable(of Employee) with a ParentID/ChildID relationship with itself that I can databind to a TreeView and it populates the hierarchy perfectly. However, I want to be able to manually loop through all the records and create all the nodes programmatically so that I can change the attributes for each node based on the data for that given item/none.
Is there a tutorial out there that explains how to do this? I've seen many that use datasets and datatables but none that show how to do it in Linq to SQL (IEnumerable)
UPDATE:
Here's how I used to do it with a DataSet - I just can't seem to find how to do the same with IEnumerable.
Private Sub GenerateTreeView()
Dim ds As New DataSet()
Dim tasktree As New Task(_taskID)
Dim dt As DataTable = tasktree.GetTaskTree()
ds.Tables.Add(dt)
ds.Relations.Add("NodeRelation", dt.Columns("TaskID"), dt.Columns("ParentID"))
Dim dbRow As DataRow
For Each dbRow In dt.Rows
If dbRow("TaskID") = _taskID Then
Dim node As RadTreeNode = CreateNode(dbRow("Subject").ToString(), False, dbRow("TaskID").ToString())
RadTree1.Nodes.Add(node)
RecursivelyPopulate(dbRow, node)
End If
Next dbRow
End Sub
Private Sub RecursivelyPopulate(ByVal dbRow As DataRow, ByVal node As RadTreeNode)
Dim childRow As DataRow
Dim StrikeThrough As String = ""
Dim ExpandNode As Boolean = True
For Each childRow In dbRow.GetChildRows("NodeRelation")
Select Case childRow("StatusTypeID")
Case 2
StrikeThrough = "ActiveTask"
Case 3
StrikeThrough = "CompletedTask"
ExpandNode = False
Case 4, 5
StrikeThrough = "ClosedTask"
ExpandNode = False
Case Else
StrikeThrough = "InactiveTask"
ExpandNode = False
End Select
Dim childNode As RadTreeNode = CreateNode("<span class=""" & StrikeThrough & """>" & childRow("Subject").ToString() & "</span>", ExpandNode, childRow("TaskID").ToString())
node.Nodes.Add(childNode)
RecursivelyPopulate(childRow, childNode)
ExpandNode = True
Next childRow
End Sub
Private Function CreateNode(ByVal [text] As String, ByVal expanded As Boolean, ByVal id As String) As RadTreeNode
Dim node As New RadTreeNode([text])
node.Expanded = expanded
Return node
End Function
If you just need a way of enumerating the tree you can implement this as a generator, it might look strange, you're probably better of with a user defined enumerator but it's essentially the same thing.
public interface IGetChildItems<TEntity>
{
IEnumerable<TEntity> GetChildItems();
}
public static IEnumerable<TEntity> Flatten<TEntity>(TEntity root)
where TEntity : IGetChildItems<TEntity>
{
var stack = new Stack<TEntity>();
stack.Push(root);
while (stack.Count > 0)
{
var item = stack.Pop();
foreach (var child in item.GetChildItems())
{
stack.Push(child);
}
yield return item;
}
}
The type constraint where TEntity : IGetChildItems is just to signify that you need to abstract how to descend the hierarchy. Without the above code would not compile.
This will enumerate the tree in a breadth first fashion, it will yield the parent element first then it's children, and then the children of those children. You can easily customize the above code to achieve a different behavior.
Edit:
The yield return stuff tells the compiler that it should return a value then continue. yield is a context keyword and it's only allowed inside an iterative statement. A generator is a simple way of writing a IEnumerable data source. The compiler will build a state machine from this code and create an enumerable anonymous class. Apparently the yield keyword does not exist in VB.NET. But you can still write a class which does this.
Imports System
Imports System.Collections
Imports System.Collections.Generic
Public Class HierarchyEnumerator(Of TEntity As IGetChildItems(Of TEntity))
Implements IEnumerator(Of TEntity), IDisposable, IEnumerator
Public Sub New(ByVal root As TEntity)
Me.stack = New Stack(Of TEntity)
Me.stack.Push(root)
End Sub
Public Sub Dispose()
End Sub
Public Function MoveNext() As Boolean
Do While (Me.stack.Count > 0)
Dim item As TEntity = Me.stack.Pop
Dim child As TEntity
For Each child In item.GetChildItems
Me.stack.Push(child)
Next
Me.current = item
Return True
Loop
Return False
End Function
Public Sub Reset()
Throw New NotSupportedException
End Sub
Public ReadOnly Property Current() As TEntity
Get
Return Me.current
End Get
End Property
Private ReadOnly Property System.Collections.IEnumerator.Current As Object
Get
Return Me.Current
End Get
End Property
Private current As TEntity
Private stack As Stack(Of TEntity)
End Class