Search code examples
jsonvb.netjson.netjson-deserialization

Deserialize JSON string which is array and has illegal property names


I'm using JSON.NET to deserialize JSON responses from HTTP queries, but I have a problem on deserialization.

The source JSON is like that:

[
  {
    "type": "rpc",
    "tid": 18,
    "action": "TaskSystem",
    "method": "createTask",
    "result": {
      "0": {
        "success": true,
        "successes": [
          [
            "Task successfuly created with S/N #22920"
          ]
        ]
      },
      "1": {
        "success": true,
        "successes": [
          [
            "Task successfuly created with S/N #22921"
          ],
          "Task #22921 marked as urgent"
        ]
      },
      "records": [
        {
          "id": 22920
        },
        {
          "id": 22921
        }
      ],
      "success": true
    }
  }
]

I've been using these classes for deserialization:

Private Sub Deserialize()
    Dim Jobj = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Response())(Jstring)
End Sub

Public Class Record
    Public Property id As Integer
End Class

Public Class Result
    Public Property records As Record()
    Public Property success As Boolean
End Class

Public Class Response
    Public Property type As String
    Public Property tid As Integer
    Public Property action As String
    Public Property method As String
    Public Property result As Result
End Class

But then I am losing the success/failure messages returned by the query

How should I write the class Result in order to colect the properties records As Record(), succes As Boolean and also those objects named "0", "1" and so on...?

Thank you very much for any help.


Solution

  • You have two unrelated problems here:

    1. Your Result class consists of a set of fixed properties and a set of variable properties with incremented numeric names and a standardized schema for the values. You would like to deserialize the standard properties automatically and capture and deserialize the custom properties as well.

      This can be done using JsonExtensionData. With this attribute, you can temporarily deserialize the custom properties to a Dictionary(of String, JToken) then later convert to a Dictionary(Of String, Success) in an [OnDeserialized] callback. Here Success is to be a type designed to capture JSON like this:

      {
        "success": true,
        "successes": [ [ "Task successfuly created with S/N #22920" ] ]
      }
      

      For documentation, see Deserialize ExtensionData.

    2. Inside the abovementioned Success type, the "successes" array contains both arrays of strings and individual strings.

      If you define your successes property as follows:

      Public Property successes As List(Of List(Of String))
      

      Then this can be handled using a variation of the SingleOrArrayConverter(Of String) from How to handle both a single item and an array for the same property using JSON.net, setting it as the item converter via <JsonProperty(ItemConverterType := GetType(SingleOrArrayConverter(Of String)))>.

    Thus your final classes would look like:

    Public Class Success
        Public Property success As Boolean
        <JsonProperty(ItemConverterType := GetType(SingleOrArrayConverter(Of String)))> _   
        Public Property successes As List(Of List(Of String))
    End Class
    
    Public Class Record
        Public Property id As Integer
    End Class
    
    Public Class Result
        Public Property records As Record()
        Public Property success As Boolean
    
        <JsonIgnore> _
        Public Property successes as Dictionary(Of string, Success)
    
        <JsonExtensionData> _
        Private _additionalData as Dictionary(Of string, JToken)
    
        <System.Runtime.Serialization.OnSerializing> _
        Sub OnSerializing(ByVal context as System.Runtime.Serialization.StreamingContext)
            If successes IsNot Nothing
                _additionalData = successes.ToDictionary(Function(p) p.Key, Function(p) JToken.FromObject(p.Value))
            Else
                _additionalData = Nothing
            End If
        End Sub
    
        <System.Runtime.Serialization.OnSerialized> _
        Sub OnSerialized(ByVal context as System.Runtime.Serialization.StreamingContext)
            _additionalData = Nothing
        End Sub
    
        <System.Runtime.Serialization.OnDeserializing> _
        Sub OnDeserializing(ByVal context as System.Runtime.Serialization.StreamingContext)
            _additionalData = Nothing
        End Sub
    
        <System.Runtime.Serialization.OnDeserialized>
        Sub OnDeserialized(ByVal context as System.Runtime.Serialization.StreamingContext)
    
            If _additionalData IsNot Nothing
                successes = _additionalData.ToDictionary(Function(p) p.Key, Function(p) p.Value.ToObject(Of Success)())
            End If
            _additionalData = Nothing
    
        End Sub
    End Class
    
    Public Class Response
        Public Property type As String
        Public Property tid As Integer
        Public Property action As String
        Public Property method As String
        Public Property result As Result
    End Class
    
    Public Class SingleOrArrayConverter(Of T)
        Inherits JsonConverter
    
        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
    
        Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
            Dim retVal As Object = New List(Of T)()
            If reader.TokenType = JsonToken.StartArray Then
                serializer.Populate(reader, retVal)
            Else
                Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
                retVal.Add(instance)
            End If
            Return retVal
        End Function
    
        Public Overrides Function CanConvert(objectType As Type) As Boolean
            Return objectType = GetType(List(Of T))
        End Function
    
    End Class
    

    Prototype fiddle.