VBobCat VBobCat - 6 months ago 50
JSON Question

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.

dbc dbc
Answer

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.

Comments