How to deserialize JSON which can be an array or a single object
Solution 1
Here is how to get the SingleOrArrayConverter
solution in the linked duplicate question working for your use case.
First, here is the VB-translated converter code. Take this and save it to a class file somewhere in your project. You can then easily reuse it for any future cases like this.
Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq
Public Class SingleOrArrayConverter(Of T)
Inherits JsonConverter
Public Overrides Function CanConvert(objectType As Type) As Boolean
Return objectType = GetType(List(Of T))
End Function
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim token As JToken = JToken.Load(reader)
If (token.Type = JTokenType.Array) Then
Return token.ToObject(Of List(Of T))()
End If
Return New List(Of T) From {token.ToObject(Of T)()}
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
Now that you have this converter, any time you have a property that can be either a list or a single item, all you have to do is declare it as a list in your class and then annotate that list with a JsonConverter
attribute such that it uses the SingleOrArrayConverter
class. In your case, that would look like this:
Public Class jsonCar
Public Property make As String
Public Property model As String
<JsonConverter(GetType(SingleOrArrayConverter(Of jsonCarLines)))>
Public Property lines As List(Of jsonCarLines)
Public Property year As String
End Class
Then, just deserialize as you normally would, and it works as expected.
Dim car As jsonCar = JsonConvert.DeserializeObject(Of jsonCar)(json)
Here is a complete demonstration: https://dotnetfiddle.net/msYNeQ
Solution 2
Thanks to both crowcoder & Kundan. I combined the two approaches and came up with something that works with both json inputs. Here is the final code.
Public Class jsonCar
Public Property make As String
Public Property model As String
Public Property linesArray As List(Of jsonCarLines)
Public Property year As String
End Class
Public Class jsonCarLines
Public Property line As String
Public Property engine As String
Public Property color As String
End Class
Module Module1
'Private Const json As String = "{""Make"":""Dodge"",""Model"":""Charger"",""Lines"": [{""line"":""base"",""engine"": ""v6"",""color"":""red""},{""line"":""R/T"",""engine"":""v8"",""color"":""black""}],""Year"":""2013""}"
Private Const json As String = "{""Make"":""Dodge"",""Model"":""Charger"",""Lines"": {""line"":""R/T"",""engine"":""v8"",""color"":""black""},""Year"":""2013""}"
Sub Main()
Dim obj As JObject = JsonConvert.DeserializeObject(json)
Dim ln As JToken = obj("Lines")
Dim car As jsonCar = JsonConvert.DeserializeObject(Of jsonCar)(json)
If (ln.GetType() Is GetType(Newtonsoft.Json.Linq.JArray)) Then
car.linesArray = JsonConvert.DeserializeObject(Of List(Of jsonCarLines))(JsonConvert.SerializeObject(ln))
End If
If (ln.GetType() Is GetType(Newtonsoft.Json.Linq.JObject)) Then
car.linesArray = New List(Of jsonCarLines)
car.linesArray.Add(JsonConvert.DeserializeObject(Of jsonCarLines)(JsonConvert.SerializeObject(ln)))
End If
Console.WriteLine("Make: " & car.make)
Console.WriteLine("Model: " & car.model)
Console.WriteLine("Year: " & car.year)
Console.WriteLine("Lines: ")
For Each line As jsonCarLines In car.linesArray
Console.WriteLine(" Name: " & line.line)
Console.WriteLine(" Engine: " & line.engine)
Console.WriteLine(" Color: " & line.color)
Console.WriteLine()
Next
Console.ReadLine()
End Sub
End Module
Big thanks for the quick replies. This solved something I'd been spending a lot time off-and-on trying to figure out.
Solution 3
You could achieve this to modify your jsonCar
class like below
Public Class jsonCar
Public Property make As String
Public Property model As String
Public Property linesCollection As List(Of jsonCarLines) // Change name
Public Property lines As String // Change the type to string
Public Property year As String
End Class
And the code should be like below:
Dim car As jsonCar = JsonConvert.DeserializeObject(Of jsonCar)(json)
If (car.lines.StartsWith("[")) Then
car.linesCollection = JsonConvert.DeserializeObject(List(Of jsonCarLines))(car.lines)
Else
car.linesCollection = new List(Of jsonCarLines)
car.linesCollection.Add(JsonConvert.DeserializeObject(Of jsonCarLines)(car.lines))
EndIf
krazifan
Updated on June 04, 2022Comments
-
krazifan almost 2 years
I'm fairly new to using JSON.net and having trouble with some json I'm getting which sometime comes in as an array and sometimes as single object. Here is an example of what I'm seeing with the json
One way it comes in ...
{ "Make": "Dodge", "Model": "Charger", "Lines": [ { "line": "base", "engine": "v6", "color": "red" }, { "line": "R/T", "engine": "v8", "color": "black" } ], "Year": "2013" }
Another way it could come in
{ "Make": "Dodge", "Model": "Charger", "Lines": { "line": "base", "engine": "v6", "color": "red" }, "Year": "2013" }
Here is what I've been using for code which works on the first way and throws an exception in the second case. Been scouring the web for ways to implement this and am really stuck.
Public Class jsonCar Public Property make As String Public Property model As String Public Property lines As List(Of jsonCarLines) Public Property year As String End Class Public Class jsonCarLines Public Property line As String Public Property engine As String Public Property color As String End Class Module Module1 Private Const json As String = "{""Make"":""Dodge"",""Model"":""Charger"",""Lines"": [{""line"":""base"",""engine"": ""v6"",""color"":""red""},{""line"":""R/T"",""engine"":""v8"",""color"":""black""}],""Year"":""2013""}" 'Private Const json As String = "{""Make"":""Dodge"",""Model"":""Charger"",""Lines"": {""line"":""R/T"",""engine"":""v8"",""color"":""black""},""Year"":""2013""}" Sub Main() Dim car As jsonCar = JsonConvert.DeserializeObject(Of jsonCar)(json) Console.WriteLine("Make: " & car.make) Console.WriteLine("Model: " & car.model) Console.WriteLine("Year: " & car.year) Console.WriteLine("Lines: ") For Each ln As jsonCarLines In car.lines Console.WriteLine(" Name: " & ln.line) Console.WriteLine(" Engine: " & ln.engine) Console.WriteLine(" Color: " & ln.color) Console.WriteLine() Next Console.ReadLine() End Sub End Module
I'm guessing this will likely need a custom JsonConverter, but I'm a bit at a loss as to how to set that up.
-
Yinda Yin about 9 yearsI don't suppose the single object could be written to the JSON as an array with one object? That would greatly simplify things. In fact, that's probably the correct way to do it, if
lines
is allowed to be one or more. Otherwise, you'll have to write a custom parser, or a preprocessor that adds the requisite[]
characters. Why was this design allowed? -
Brian Rogers about 9 yearspossible duplicate of How to handle both a single item and an array for the same property using JSON.net
-
krazifan about 9 yearsunfortunately, I have no control of the json string coming in. So short of doing string manipulation, I'm stuck with what I got. I looked at the question referenced by Brian and I had issues getting it to work. I think the solution here felt more straightforward and in native VB which is bonus for me :)
-
Brian Rogers about 9 years@user4543329 Since you were unable to get the solution from the duplicate question working, I have added an answer that showing how to use the
SingleOrArrayConverter(Of T)
for your particular situation.
-
-
krazifan about 9 yearslines is never a string. It's either an array or an object. So if I change it to string, I get an invalid token exception with either version of the json.