How to parse a big JSON - 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.

Related

Search content of a JSON file with output in a DataGridView

The Form has a text field and a DataGridView. It is necessary, without displaying the entire contents of the JSON file in the DataGridView, to search the content of the JSON file and display the search result in the DataGridView.
You need to search by the UserName tag. You need to start typing either the first or last name in the text field and in the DataGridView display the result of the found.
How to read text file in DataGridView I know:
Imports System.IO
Imports Newtonsoft.Json
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim result = JsonConvert.DeserializeObject(Of List(Of Users))(File.ReadAllText("D:\Users.json"))
DataGridView1.DataSource = result
End Sub
Public Class Users
Public Property ID() As Integer
Public Property UserName() As String
Public Property Login() As String
Public Property Title() As String
Public Property Dep() As String
Public Property Mail() As String
Public Property Phone() As String
End Class
End Class
I also know how to do a file search. Only for some reason the result displays - the first element found:
Dim json As String = File.ReadAllText("D:\Users.json")
Dim objectList = JsonConvert.DeserializeObject(Of List(Of Users))(json)
Dim foundItem = objectList.Where(Function(underscore) underscore.UserName.Contains("Tom")).FirstOrDefault()
If foundItem IsNot Nothing Then
MessageBox.Show(foundItem.UserName)
Else
MsgBox("none")
End If
And the actual contents of the json file itself:
[
{
"id":"1",
"UserName":"Fred Smith",
"Login":"f.smith",
"Title":"engineer",
"Dep":"IT infrastcomcture",
"Mail":"f.smith#domain.com",
"Phone":"111",
},
{
"id":"2",
"UserName":"Ben Taylor",
"Login":"b.taylor",
"Title":"programmer",
"Dep":"IT infrastcomcture",
"Mail":"b.taylor#domain.com",
"Phone":"100",
},
{
"id":"3",
"UserName":"Steve Harris",
"Login":"s.harris",
"Title":"System Administrator",
"Dep":"IT infrastcomcture",
"Mail":"s.harris#domain.com",
"Phone":"263",
},
{
"id":"4",
"UserName":"Tom Walker",
"Login":"t.walker",
"Title":"engineer",
"Dep":"IT infrastcomcture",
"Mail":"t.walker#domain.com",
"Phone":"263",
},
{
"id":"5",
"UserName":"Tom Davis",
"Login":"t.davis",
"Title":"engineer",
"Dep":"IT infrastcomcture",
"Mail":"t.davis#domain.com",
"Phone":"200",
},
{
"id":"6",
"UserName":"Ben Walker",
"Login":"b.walker",
"Title":"System Administrator",
"Dep":"IT infrastcomcture",
"Mail":"b.walker#domain.com",
"Phone":"167",
},
]
A few things to note:
The JSON presented here represent an array of objects which all have the same properties. It can be considered an array of records or Rows.
You need to deserialize this JSON, present the result in a DataGridView and allow the user to filter and probably sort the data.
You're currently deserializing this JSON to simple collection a class objects, which is perfectly fine. It may become a little more complex if you want to filter and sort this collection, since a simple List<T> doesn't support it by itself. Nor does a BindingList.
You should implement the IBindingListView interface in a class that handles the List of objects and most probably also the INotifyPropertyChanged interface in the base class (your current Users class).
Or use an ORM / Mini-ORM instead.
There's an already built (and tested) Type that already implements all these features, the DataTable class.
Since, as mentioned, your JSON IS actually a Table (an array of records), deserializing it to a DataTable is quite straightforward. It's just:
Dim dt = JsonConvert.DeserializeObject(Of DataTable)(json)
The DataTable class already allows filtering, setting its DefaultView.RowFilter property and sorting, setting its DefaultView.Sort property.
Nonetheless, I suggest to use a BindingSource as mediator between the DataTable and the UI.
This tool is quite useful, since it provides common methods to filter and sort a source of data, provided that the source of data actually has these capabilities.
Using a BindingSource, you always use the same methods, no matter what the source of data is.
It also generates some useful events, as the ListChanged, AddingNew, CurrentChanged events and more.
The ListChanged event also provides arguments that specify the type of change.
With a BindingSource, to serialize back to JSON, if the the data has changed:
[BindingSource].EndEdit()
Dim json = JsonConvert.SerializeObject([BindingSource].DataSource, Formatting.Indented)
In the sample code, these objects are used (see the visual example):
UsersSource: the BindingSource object
tBoxFilterUser: a TextBox Control, filters the data using the UserName Property
tBoxFilterTitle: a TextBox Control, filters the data using the Title Property
btnRemoveFilter: a Button Control used to remove the current filters
dgv: a DataGridView Control
Public Class SomeForm
Private UsersSource As New BindingSource()
' Current filters
Private UserNameFilter As String = "UserName LIKE '%%'"
Private UserTitleFilter As String = "Title LIKE '%%'"
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
Dim json = File.ReadAllText("D:\Users.json")
Dim dt = JsonConvert.DeserializeObject(Of DataTable)(json)
dt.AcceptChanges()
UsersSource.DataSource = dt
dgv.DataSource = UsersSource
End Sub
Private Sub tBoxFilterUser_TextChanged(sender As Object, e As EventArgs) Handles tBoxFilterUser.TextChanged
Dim tbox = DirectCast(sender, TextBox)
UserNameFilter = $"UserName LIKE '%{tbox.Text}%'"
UsersSource.Filter = $"{UserNameFilter} AND {UserTitleFilter}"
End Sub
Private Sub tBoxFilterTitle_TextChanged(sender As Object, e As EventArgs) Handles tBoxFilterTitle.TextChanged
Dim tbox = DirectCast(sender, TextBox)
UserTitleFilter = $"Title LIKE '%{tbox.Text}%'"
UsersSource.Filter = $"{UserNameFilter} AND {UserTitleFilter}"
End Sub
Private Sub btnRemoveFilter_Click(sender As Object, e As EventArgs) Handles btnRemoveFilter.Click
tBoxFilterUser.Clear()
tBoxFilterTitle.Clear()
UsersSource.RemoveFilter()
End Sub
End Class
This is how it works:

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

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.

Deserialize JSON keep getting null referance exception

Okay, so I want to deserialize the json from https://openrct.net/ajax/chat.php. Problem is that no matter what I try, I end up with a Null Reference Exception when I try to access the stored data. I have been trying for close to an hour, googling and trying different things, and I am completely at a loss for how to do this. Please help me out.
Imports Newtonsoft
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Imports System.Net
Public Class Form1
Dim WS As New WebClient
'/ajax/chat.php
'And /ajax/chat.php?latest=55 to get all chat messages after that ID
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Public Class PostWrapper
Public posts() As Post
End Class
Public Class Post
Public Property a() As String
Public Property m() As String
Public Property t() As String
End Class
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim json As String = WS.DownloadString("https://openrct.net/ajax/chat.php")
MsgBox(json)
Dim postWrapper = JsonConvert.DeserializeObject(Of PostWrapper)(json) ' Deserialize array of Post objects
MsgBox(postWrapper.posts(0).m) 'Errors Here.
End Sub
End Class
Thank you.
Your object classes for deserializing are wrong, you can use jsonutils.com for generating classes from json string or URL.
Here is the classes for your json,
Public Class M
Public Property id As String
Public Property a As String
Public Property m As String
Public Property t As String
End Class
Public Class Example
Public Property m As M()
Public Property l As String
End Class
Dim postWrapper = JsonConvert.DeserializeObject(Of Example)(json)
MsgBox(postWrapper.m)
I am not .Net developer but what i can suggest is you should check for authentication details while calling for this link as its https, which means its secured line so it may be refusing your connection. Link

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