How to deserialize JSON which can be an array or a

2020-03-23 05:59发布

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.

4条回答
时光不老,我们不散
2楼-- · 2020-03-23 06:16

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

查看更多
Deceive 欺骗
3楼-- · 2020-03-23 06:23

You can generically deserialize to Object and then inspect what you have. You can check for a JArray or JObject and act accordingly. You don't even need to deserialize into a specific type, you can work with the Dynamic objects but that may not be the best idea.

    Module Module1

    Sub Main()
     Dim jsonWithArray As String = "{""Make"":""Dodge"",""Model"":""Charger"",""Lines"": [{""line"":""base"",""engine"": ""v6"",""color"":""red""},{""line"":""R/T"",""engine"":""v8"",""color"":""black""}],""Year"":""2013""}"
     Dim jsonWithObject As String = "{""Make"":""Dodge"",""Model"":""Charger"",""Lines"": {""line"":""base"",""engine"": ""v6"",""color"":""red""},""Year"":""2013""}"

     Dim witharray As Newtonsoft.Json.Linq.JObject = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonWithArray)
     Dim withstring As Newtonsoft.Json.Linq.JObject = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonWithObject)

     Dim jtokArray As Newtonsoft.Json.Linq.JToken = witharray("Lines")
     Dim jtokStr As Newtonsoft.Json.Linq.JToken = withstring("Lines")

     If (jtokArray.GetType() Is GetType(Newtonsoft.Json.Linq.JArray)) Then
        Console.WriteLine("its an array")
     End If

     If (jtokStr.GetType() Is GetType(Newtonsoft.Json.Linq.JObject)) Then
        Console.WriteLine("its an object")
     End If

    Console.ReadKey()

    End Sub

End Module
查看更多
▲ chillily
4楼-- · 2020-03-23 06:39

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.

查看更多
冷血范
5楼-- · 2020-03-23 06:43

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
查看更多
登录 后发表回答