2

Wondering if you could help me create a VB.Net class into which I can deserialize the following JSON response:

{
  "id":86,
  "name":"Tom",
  "likes":
         {
         "actors":[
                    ["Clooney",2,30,4],
                    ["Hanks",104,15,1]
                  ]
         },
  "code":8
}

I have the following:

Class mLikes

    Public actors As IList(Of IList(Of String))

end Class

and

Class Player

    <JsonProperty(PropertyName:="id")>
    Public Id As Integer

    <JsonProperty(PropertyName:="name")>
    Public Name As String

    <JsonProperty(PropertyName:="likes")>
    Public Likes As mLikes

    <JsonProperty(PropertyName:="code")>
    Public Code As Integer

End Class

I am using Newtonsoft.Json to deserialize:

Result = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Player)(jsonResponse)

If I know that the actors elements always follow the same format -

Class Actor
  Public Name as String
  Public NumberOfMovies as Integer
  Public NumberOfAwards as Integer
  Public NumberOfTVshows as Integer
End Class 

Is there a way I can parse the JSON response so that Player.Likes.Actors is a List(Of Actor) instead of a List(Of List(Of String)) which is what I have now?

Siguza
  • 21,155
  • 6
  • 52
  • 89
Freemo
  • 23
  • 1
  • 3
  • actually, `Actors` looks like an `Object`. Each actor item has a string for the same, but then there are those 3 ints stuffed in there with it. They arent "named" so the problem is getting them to map to anything meaningful. If all you want are the names, you could parse it and fish them out – Ňɏssa Pøngjǣrdenlarp Aug 09 '15 at 15:48

1 Answers1

0

What you can do is to create a JsonConverter that serializes your Actor class as an IEnumerable<object> in the correct order, then in deserialization reads the JSON using LINQ to JSON, checks that the token read is an array, then sets the properties in the equivalent order.

You could hardcode this for your Actor class, but I think it's more interesting to create a generic converter that converts non-enumerable POCO from and to a JSON array using the POCO type's property order. This order can be specified in your class using the <JsonProperty(Order := NNN)> attribute.

Thus, the converter:

Public Class ObjectToArrayConverter(Of T)
    Inherits JsonConverter

    Public Overrides Function CanConvert(objectType As Type) As Boolean
        Return GetType(T) = objectType
    End Function

    Private Shared Function ShouldSkip(p As JsonProperty) As Boolean
        Return p.Ignored Or Not p.Readable Or Not p.Writable
    End Function

    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        If value Is Nothing Then
            writer.WriteNull()
        Else
            Dim type = value.GetType()
            Dim contract = TryCast(serializer.ContractResolver.ResolveContract(type), JsonObjectContract)
            If contract Is Nothing Then
                Throw New JsonSerializationException("invalid type " & type.FullName)
            End If
            Dim list = contract.Properties.Where(Function(p) Not ShouldSkip(p)).Select(Function(p) p.ValueProvider.GetValue(value))
            serializer.Serialize(writer, list)
        End If
    End Sub

    Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
        If reader.TokenType = JTokenType.Null Then
            Return Nothing
        End If
        Dim token = JArray.Load(reader)
        Dim contract = TryCast(serializer.ContractResolver.ResolveContract(objectType), JsonObjectContract)
        If contract Is Nothing Then
            Throw New JsonSerializationException("invalid type " & objectType.FullName)
        End If
        Dim value = If(existingValue, contract.DefaultCreator()())
        For Each pair In contract.Properties.Where(Function(p) Not ShouldSkip(p)).Zip(token, Function(p, v) New With { Key.Value = v, Key.Property = p })
            Dim propertyValue = pair.Value.ToObject(pair.Property.PropertyType, serializer)
            pair.Property.ValueProvider.SetValue(value, propertyValue)
        Next
        Return value
    End Function
End Class

And your class:

<JsonConverter(GetType(ObjectToArrayConverter(Of Actor)))> _
Public Class Actor
    ' Use [JsonProperty(Order=x)] //http://www.newtonsoft.com/json/help/html/JsonPropertyOrder.htm to explicitly set the order of properties
    <JsonProperty(Order := 0)> _
    Public Property Name As String

    <JsonProperty(Order := 1)> _
    Public Property NumberOfMovies As Integer

    <JsonProperty(Order := 2)> _
    Public Property NumberOfAwards As Integer

    <JsonProperty(Order := 3)> _
    Public Property NumberOfTVshows As Integer
End Class

Working fiddle.

Note an updated c# version that handles JsonConverter attributes applied to properties can be found here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • This is great ,thank you so much for the detailed reply. The problem I'm having implementing your code above now is the line: Dim propertyValue = pair.Value.ToObject(pair.Property.PropertyType, serializer) in the converter.... I get the error: "Overload resolution failed because no accessible 'ToObject' accepts this number of arguments". Any ideas? – Freemo Aug 09 '15 at 21:14
  • @Freemo - I'm using [`JToken.ToObject(Type, JsonSerializer)`](http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Linq_JToken_ToObject_1.htm) which is present in the current version of Json.NET (and also seems present as far back as Json.NET 4.5.11). Can you update your question to show your code, or upload it to a pastebin somewhere? The converter is working in the [linked fiddle](https://dotnetfiddle.net/9Sw9Sw). – dbc Aug 09 '15 at 22:01
  • I updated to the latest version of Json.NET and it works perfectly. You're a genius! Thanks so much - you have saved my weekend. All the best – Freemo Aug 10 '15 at 03:07