How to extract a specific object from a JSON? - json

I would like to know how to extract a specific object from a JSON.
I saw most of the problem solved on Stackoverflow before posting this, but there is no one who already talked about this.
I want need to get the slug value from the JSON objects.
Here is my code Get Users From JSON
Imports System
Imports Newtonsoft.Json.Linq
Public Module Module1
Public Sub Main()
Dim myJsonString = New System.IO.StreamReader(New System.Net.WebClient().
OpenRead("https://pastebin.com/raw/z4GZFuF3")).ReadToEnd()
Dim myJObject = JObject.Parse(myJsonString)
For Each match In myJObject("matches")
Console.WriteLine(match("id")("slug"))
Next
End Sub
End Module
And Here is the Output:
Run-time exception (line -1): Error reading JObject from JsonReader.
Current JsonReader item is not an object: StartArray. Path '', line 1, position 1.
Stack Trace:
[Newtonsoft.Json.JsonReaderException: Error reading JObject from JsonReader.
Current JsonReader item is not an object: StartArray. Path '', line 1, position 1.]
at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader, JsonLoadSettings settings)
at Newtonsoft.Json.Linq.JObject.Parse(String json, JsonLoadSettings settings)
at Newtonsoft.Json.Linq.JObject.Parse(String json)
at Module1.Main()
What I have reduced since this error is that the object "matches" does not exist in the JSON text, but I have no idea what I should specify in its place to make this work.

The JSON that can be retrieved from the provided address:
(http://www.stginternational.org/wp-json/wp/v2/users)
is an array of Objects.
It can be parsed using JArray.Parse(), but I suggest to deserialize this JSON as .Net classes: it's much easier to handle.
The JSON's base object (each object in the array) is defined like this:
{
"id": 1,
"name": "drall",
"url": "",
"description": "",
"link": "http://www.stginternational.org/author/drall/",
"slug": "drall",
"avatar_urls": {
"24": "http://1.gravatar.com/avatar/dc6dd0ef71784957b629e124f19364cb?s=24&d=mm&r=g",
"48": "http://1.gravatar.com/avatar/dc6dd0ef71784957b629e124f19364cb?s=48&d=mm&r=g",
"96": "http://1.gravatar.com/avatar/dc6dd0ef71784957b629e124f19364cb?s=96&d=mm&r=g"
},
"meta": [],
"_links": {
"self": [
{
"href": "http://www.stginternational.org/wp-json/wp/v2/users/1"
}
],
"collection": [
{
"href": "http://www.stginternational.org/wp-json/wp/v2/users"
}
]
}
}
It can be represented by these .Net classes:
Public Class UserObject
Public Property Id As Long
Public Property Name As String
Public Property Url As String
Public Property Description As String
Public Property Link As Uri
Public Property Slug As String
<JsonProperty("avatar_urls")>
Public Property AvatarUrls As Dictionary(Of String, Uri)
Public Property Meta As List(Of Object)
<JsonProperty("_links")>
Public Property Links As Links
End Class
Public Class Links
Public Property Self As List(Of LinkCollection)
Public Property Collection As List(Of LinkCollection)
End Class
Public Class LinkCollection
Public property Href As Uri
End Class
With this model, you can simply use JsonConvert.DeserializeObject(), specifying the Type to deserialize to.
As mentioned, this is an Array or List of objects, where the base object is an UserObject, so you can specify a List(Of UserObject) :
Dim json = JsonConvert.DeserializeObject(Of List(Of UserObject))(json)
You can then access the class object as usual:
Imports System.Net
Imports Newtonsoft.Json
Dim users As List(Of UserObject) = Nothing
Using client As New WebClient()
Dim json = client.DownloadString([The URL])
users = JsonConvert.DeserializeObject(Of List(Of UserObject))(json)
End Using
If users IsNot Nothing Then
For Each user In users
Console.WriteLine(user.Slug)
Console.WriteLine(user.Links.Self(0).Href)
Console.WriteLine(user.Links.Collection(0).Href)
For Each avatar In user.AvatarUrls
Console.WriteLine($"Key: {avatar.Key}, Value: {avatar.Value}")
Next
Next
End If
In case you just want one of the properties (slug, in this case), you can use JArray.Parse() to parse the JSON and read the property value directly:
Using client As New WebClient()
Dim json = client.DownloadString([The URL])
Dim users = JArray.Parse(json)
For Each user As JToken In users
Console.WriteLine(user("slug"))
Next
End Using

While Jimi's answer is preferable because it deserializes the JSON into a strongly typed object, here is an alternative since you only care about getting a single property from the array of objects.
It does the following three steps:
Get the JSON from the endpoint
Convert the JSON literal into JArray
Use LINQ to get just the Slug item of each object in the array
Dim myJsonString = New System.IO.StreamReader(New System.Net.WebClient().OpenRead("http://www.stginternational.org/wp-json/wp/v2/users")).ReadToEnd
Dim arrayOfObjects = JArray.Parse(myJsonString)
Dim arrayOfSlugs = From jsonObject In arrayOfObjects Select jsonObject.Item("slug")
Example: Live Demo

Related

JSON deserialization error with Azure translation services

I am building a program in Visual Studio 2017 in Windows Forms - sorry but that's the only thing I know how to use - anyway, most everything for this is C#, so I've been having trouble getting help.
I have translated the Microsoft provided example for a C# program to connect to Azure Cognitive Translation services, signed up, got all my keys, etc.
When I run the code, I get the following error:
Newtonsoft.Json.JsonSerializationException:
'Cannot deserialize the
current JSON object (e.g. {"name":"value"}) into type
System.Collections.Generic.List1[System.Collections.Generic.Dictionary2[System.String,System.Collections.Generic.List1[System.Collections.Generic.Dictionary2[System.String,System.String]]]]'
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 'error', line 1, position 9.'
I have tried too many things to list from many different sources. I do not know a whole lot about JSON and am asking for help with the code to solve the above issue.
Public Class DetectedLanguage
Public Property language As String
Public Property score As Double
End Class
Public Class Translation
Public Property text As String
Public Property two As String
End Class
Public Class Example
Public Property detectedLanguage As DetectedLanguage
Public Property translations As Translation()
End Class
Dim textToTranslate As String = root
Dim fromLanguage As String
Dim fromLanguageCode As String = cabbr
Dim toLanguageCode As String = "en"
Dim endpoint As String = String.Format(TEXT_TRANSLATION_API_ENDPOINT, "translate")
Dim uri As String = String.Format(endpoint & "&from={0}&to={1}", fromLanguageCode, toLanguageCode)
Dim body As System.Object() = New System.Object() {New With {Key .Text = textToTranslate}}
Dim requestBody = JsonConvert.SerializeObject(body)
Using client = New HttpClient()
Using request = New HttpRequestMessage()
request.Method = HttpMethod.Post
request.RequestUri = New Uri(uri)
request.Content = New StringContent(requestBody, Encoding.UTF8, "application/json")
request.Headers.Add("Ocp-Apim-Subscription-Key", COGNITIVE_SERVICES_KEY)
request.Headers.Add("Ocp-Apim-Subscription-Region", "westus")
request.Headers.Add("X-ClientTraceId", Guid.NewGuid().ToString())
Dim response = client.SendAsync(request).Result
Dim responseBody = response.Content.ReadAsStringAsync().Result
Dim result = JsonConvert.DeserializeObject(Of List(Of Dictionary(Of String, List(Of Dictionary(Of String, String)))))(responseBody)
Dim translation = result(0)("translations")(0)("text")
rtRoot.Text = translation
End Using
End Using
I have already used the jsonutil site to paste my JSON code in and get the classes.
Here is my JSON content:
[
{
"detectedLanguage":{
"language":"nl",
"score":1.0
},
"translations":[
{
"text":"bord vervangen en uitvoerig getest",
"to":"nl"
},
{
"text":"Board replaced and tested extensively",
"to":"en"
}
]
}
]
OK!!! after playing around with this - Jimi - your solution worked!!! thank you SO much! i had to remove the following to lines: request.Headers.Add("Ocp-Apim-Subscription-Region", "westus") request.Headers.Add("X-ClientTraceId", Guid.NewGuid().ToString())

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
}
}

Parse json string to object without using any third party library in vb.net

I'm trying to use JavaScriptSerializer to parse some json string to create objects. I'm thinking about implementing something like this but no luck on my end...
Dim object As New SomeObject
Dim js As New JavaScriptSerializer
someObject = js.Deserialize(Of Somebject)(jsonstr)
object1.var1 = someObject("somekey")
where jsonstr is data in json format. I can't use any third party library like newtonsoft.json etc.
edit: I made some progress, given my class e.g. has property "name" and my json has key "name" as well it will pass on the string to objects property with whatever value the json file has.
Dim object As SomeObject = js.Deserialize(Of SomeObject)(jsonstr)
Dim name As String = object.name //object now holds value for name
but if my json is like this. How can I get name in the following?
"value" : {
"validationFactors" : [
{
"name" : "remote_address",
"value" : "127.0.0.1"
}
]
}
If I call Deserialize to get name for e.g I get MissingMethodException
Do I need to change my dummy object and if so how? Thanks
Here's an example that works:
Sub Main
Dim js As New JavaScriptSerializer()
Dim someObject As SomeObject = js.Deserialize(Of SomeObject)("{""A"":""Hello"",""B"":42}")
Console.WriteLine("A:={0}, B:={1}", someObject.A, someObject.B)
End Sub
Class SomeObject
Public Property A As String
Public Property B As Integer
End Class
Output:
A:=Hello, B:=42
However, it looks like you are trying to access someObject like a dictionary, and you could do that with:
Dim someObject As Dictionary(Of String, Object) = js.Deserialize(Of Dictionary(Of String, Object))("{""A"":""Hello"",""B"":42}")
Console.WriteLine("A:={0}, B:={1}", someObject("A"), someObject("B"))
Same output as before.

Deserialize Json to List(of Integer)

I'm trying to deserialize some Json to a .Net class. I'm having a problem with the contacttypeidlist field.
How can I change the Json to get this to work?
I am receiving the following error:
Cannot convert object of type 'System.String' to type'System.Collections.Generic.List`1[System.Int32]'
Json
[
{
"fullname": "bob smith",
"email": "bob#test.com",
"phone": "555-5555",
"onsite": "true",
"contacttypeidlist": "[1,2,3]"
}
]
Deserialize code
Dim oSerializerSort As System.Web.Script.Serialization.JavaScriptSerializer = New System.Web.Script.Serialization.JavaScriptSerializer()
Dim lobjSiteContactsList = oSerializerSort.Deserialize(Of List(Of SiteContact))(lstrSiteContactsJson)
SiteContact Class Definition
Public Property fullname As String
Public Property email As String
Public Property phone As String
Public Property onsite As Boolean
Public Property contacttypeidlist As List(Of Integer)
The "contacttypeidlist" property in your JSON is just a simple plain string. So make sure you respect this in your model:
Public Property contacttypeidlist As String
On the other hand if your JSON looked like this (notice the missing double quotes around the value):
"contacttypeidlist": [1,2,3]
you could have used an integer array in your VB.NET model.
If you have no control over the original JSON and cannot fix its format so that the contacttypeidlist property represents an actual integer array instead of a string then you have to perform the deserialization in 2 steps: first deserialize the original JSON into a structure with a string property. And in a second step deserialize this string property to an integer array.

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)