JsonConvert.DeserializeObject knowing class name (string) - json

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

Related

How to parse a big JSON

I'm trying to parse a big JSON and I only need few things from it.
I've been trying to beautify it but I'm having trouble also because it seems that's not completely correct.
This is the website
and I've tried to delete all the unnecessary json
ending up with
[
[
"updateGlobalData",
{
"backgroundData":{
"cities":[
{
"type":"city",
"name":"Polis",
"id":186979,
"level":35,
"ownerId":"99253",
"ownerName":"D3vil666",
"ownerAllyId":"0",
"hasTreaties":0,
"actions":[
],
"state":"vacation",
"viewAble":1,
"infestedByPlague":false
},
{
"type":"city",
"name":"London",
"id":378440,
"level":28,
"ownerId":"242906",
"ownerName":"Mattia",
"ownerAllyId":"5541",
"ownerAllyTag":"LORDS",
"hasTreaties":0,
"actions":[
],
"state":"",
"viewAble":2,
"infestedByPlague":false
}
]
}
}
]
]
The properties I'm looking for are "State" and "ownerName".
State can be "vacation" like ""state"": ""vacation"" or "" like ""state"": """"
These properties are often repeated so I'm only looking for to parse them only once.
For example:
I want to know which state Mattia has and it should return "nothing".
Which state D3vil666 has and it should return "vacation".
The code I'm using:
Public Class Form1
Public Class GlobalData
Public Class BackgroundData
Public Property Cities As List(Of City)
End Class
Public Class City
Public Property Type As String
Public Property Name As String
Public Property OwnerName As String
Public Property State As String
End Class
Public Property BackgroundDataa As BackgroundData
End Class
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim client As New WebClient()
Dim jsonString As String = client.DownloadString("https://s10-it.ikariam.gameforge.com/?view=updateGlobalData&islandId=1649&backgroundView=island&currentIslandId=1649&actionRequest=90983fef22b312ff7cbd51f0183bc301&ajax=1")
Dim data As List(Of GlobalData) = JsonConvert.DeserializeObject(Of List(Of GlobalData))(jsonString)
Dim state = data(0).BackgroundDataa.Cities.FirstOrDefault(Function(x) x.OwnerName = "Mattia").State
MsgBox(state)
End Sub
End Class
but I'm getting the error:
Newtonsoft.Json.JsonSerializationException: 'Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Ikariam_VM.Form1+GlobalData' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path '[0]', line 1, position 2.'
I'm working on winform App for .net6+ and using the nuget Newtonsoft package to handle the json.
Any idea?
Thanks
edit1:
Code after suggestions
Imports System.Net
Imports System.Net.Http
Imports System.Net.WebRequestMethods
Imports System.Runtime.InteropServices.JavaScript.JSType
Imports System.Text.Json
Imports Microsoft.Web.WebView2.Core
Imports Microsoft.Web.WebView2
Imports Microsoft.Web.WebView2.WinForms
Imports System.Threading
Imports System.IO
Imports System.Runtime.InteropServices.JavaScript
Imports Newtonsoft.Json.Linq
Imports Newtonsoft.Json
Public Class Form1
Public Class GlobalData
Public Class BackgroundData
Public Property Cities As List(Of City)
End Class
Public Class City
Public Property Type As String
Public Property Name As String
Public Property OwnerName As String
Public Property State As String
End Class
End Class
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim client As New WebClient()
Dim jsonString As String = client.DownloadString("https://s10-it.ikariam.gameforge.com/?view=updateGlobalData&islandId=1649&backgroundView=island&currentIslandId=1649&actionRequest=90983fef22b312ff7cbd51f0183bc301&ajax=1")
Dim jArr = JArray.Parse(jsonString)
Dim cities = TryCast(jArr(0).OfType(Of JObject)() _
.Select(Function(p) p.Properties().Where(Function(p) p.Name = "backgroundData").First()) _
.FirstOrDefault().Value("cities"),
JArray)
Dim state As String = Nothing
If cities IsNot Nothing Then
state = CStr(cities.Where(Function(c) CStr(c("OwnerName") = "D3vil666")) _
.FirstOrDefault()("state"))
End If
End Sub
End Class
You don't need any custom classes to get data you need from a json string you posted. I haven't a VB.Net compillator so I post my code in c#. I hope you will find somebody to tranclate it to VB.Net
var jArr = JArray.Parse(json);
var cities = jArr[0].OfType<JObject>()
.Select(p => p.Properties().Where(p => p.Name == "backgroundData").First())
.FirstOrDefault().Value["cities"] as JArray;
string state=null;
if (cities != null)
state = (string)cities.Where(c => (string)c["ownerName"] == "D3vil666")
.FirstOrDefault()["state"]; // "vacation"
Serge's answer, translated into VB:
Dim jArr = JArray.Parse(json)
Dim cities = TryCast(jArr(0).OfType(Of JObject)() _
.Select(Function(p) p.Properties().Where(Function(q) q.Name = "backgroundData").First()) _
.FirstOrDefault().Value("cities"),
JArray)
Dim state As String = Nothing
If cities IsNot Nothing Then
state = CStr(cities.Where(Function(c) CStr(c("OwnerName") = "D3vil666")) _
.FirstOrDefault()("state"))
End If
I think this is a spot where VB still needs the continuation characters to handle the lines properly.

JSON.Net Deserialization error, My Class definition is wrong?

In VB.Net program I'm getting the "Cannot deserialize the current JSON array" when using JsonConvert.DeserializeObject(Of MCMusicElements)(sRB).
The JSON array I'm working with is:
[{"Key":465419,"MIK_Energy":3,"MIK_Camelot":"9B","MIK_BPM":118}]
If I delete the "[ ]" in the JSON string my program works. So, I'm assuming my Class definition for MCMusicElements is wrong in some way. I'd like to understand how to make it work without deleting the brackets from the JSON string.
Public Class MCMusicElements
Public Property Key As Integer
Public Property MIK_Energy As Integer
Public Property MIK_Camelot As String
Public Property MIK_BPM As Integer
End Class
Dim oResult = JsonConvert.DeserializeObject(Of MCMusicElements)(sRB)
dtDataTable.Rows(index).Item(1) = oResult.MIK_Energy
dtDataTable.Rows(index).Item(2) = oResult.MIK_Camelot
dtDataTable.Rows(index).Item(3) = oResult.MIK_BPM
you have to use list of objects, not an object
Dim oResult = JsonConvert.DeserializeObject(Of List(Of MCMusicElements))(sRB)
dtDataTable.Rows(index).Item(1) = oResult(0).MIK_Energy

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.

WCF VB serialize twice

I have a WCF service with Visual Basic code (below), I need the table that getAdministrationData() returns, and Newtonsoft serializer offers me what I need, so I just don't figure out how to make that the service returns me that, and not an object serialized twice.
<OperationContract()> _
<WebInvoke(Method:="POST", _
RequestFormat:=WebMessageFormat.Json, _
BodyStyle:=WebMessageBodyStyle.Wrapped, _
UriTemplate:="getAdministrationDataTree")> _
Public Function getAdministrationDataTree() As String
Dim myAdmManager As New AdministrationManager()
Dim model As DataSet = myAdmManager.getAdministrationData()
Dim json As String = JsonConvert.SerializeObject(model)
Return json
End Function
Hope you guys throw me some light at this, and sorry for the sloppy English.
In your interface class, you should declare definitions as below:-
<OperationContract()>
<WebGet(ResponseFormat:=WebMessageFormat.Json, BodyStyle:=WebMessageBodyStyle.Wrapped, UriTemplate:="getAdministrationDataTree")>
Public Function getAdministrationDataTree() As AdminData
You also need to define a data contract like below:-
Class AdminData
<DataMember()>
Public Property AdminDataField1 as String
'Define any properties you need
End Class
Then in definition class implement your method as below:-
Public Function getAdministrationDataTree() As AdminData
Dim myAdmManager As New AdministrationManager()
Dim model As DataSet = myAdmManager.getAdministrationData()
'Create an AdminData object and fill it with values you want to return
return AdminData
End Function
You will receive a JSON object.
If you can provide more details about your the data then I can give you exact code.