vb.netinheritancejson.netextending

Extend Newtonsoft.Json.Linq.JObject Class


I want to extend Newtonsoft.Json.Linq.JObject Class in order to obtain fast access (in form of properties) to some expressions made on-the-fly from JObject's contents. Let us call the extended class JRecord.

I also need that JObject instances can be easily cast to and from the extended type JRecord. The question is: how I take a JObject instance and cast it as my extended type (which also inherits from JObject), in a way it keeps all its content but gets "dressed" with the extra properties when narrowing from JObject to JRecord and "striped" of those properties when widening from JRecord back to JObject?

Below is my first scratch and I omitted most of the properties because its string building is complex and irrelevant to the problem, which raises on both custom CType() operators, where .NET tells me that

  1. Conversion operators cannot convert from a type to its base type

  2. Conversion operators cannot convert from a base type

So, what should I do? Should I create a bare new instance and create children with the same content from those of the object I want to cast?

Public Class JRecord
    Inherits JObject

    Public Sub New()
        MyClass.New
    End Sub

    Public ReadOnly Property Id As Integer
        Get
            Return MyBase.Value(Of Integer)("id")
        End Get
    End Property

    Public ReadOnly Property NomeSigla As String
        Get
            Return String.Format(
                "{0} ({1})", 
                MyBase.Value(Of String)("nome"), 
                MyBase.Value(Of String)("sigla"))
        End Get
    End Property

    Public Overloads Shared Narrowing Operator CType(json_object As JObject) As JRecord
        Return DirectCast(json_object, JRecord)
    End Operator

    Public Overloads Shared Widening Operator CType(json_record As JRecord) As JObject
        Return json_record
    End Operator

End Class

Solution

  • I would not use inheritance and conversion operators in this case. Instead, I would use composition here. In other words, make the JRecord class wrap the original JObject and delegate to it as needed. To convert from a JObject to a JRecord, make the constructor of the JRecord accept a JObject. To go the other way, just make a JObject property on JRecord and have it return the inner JObject directly.

    Public Class JRecord
    
        Private innerJObject As JObject
    
        Public Sub New(jObject As JObject)
            innerJObject = jObject
        End Sub
    
        Public ReadOnly Property JObject As JObject
            Get
                Return innerJObject
            End Get
        End Property
    
        Public ReadOnly Property Id As Integer
            Get
                Return innerJObject.Value(Of Integer)("id")
            End Get
        End Property
    
        Public ReadOnly Property NomeSigla As String
            Get
                Return String.Format(
                    "{0} ({1})",
                    innerJObject.Value(Of String)("nome"),
                    innerJObject.Value(Of String)("sigla"))
            End Get
        End Property
    
    End Class
    

    Then you can do things like this:

    Dim jr as JRecord = new JRecord(JObject.Parse(jsonString))
    Dim id as Integer = jr.Id
    Dim ns as String = jr.NomeSigla
    Dim jo as JObject = jr.JObject
    ...
    

    If you absolutely must use inheritance, because you want to pass the JRecord directly to other places in the application that expect only a JObject, you could do this:

    Public Class JRecord
        Inherits JObject
    
        Public Sub New(jObject As JObject)
            MyBase.New(jObject)
        End Sub
    
        Public ReadOnly Property JObject As JObject
            Get
                Return Me
            End Get
        End Property
    
        Public ReadOnly Property Id As Integer
            Get
                Return Value(Of Integer)("id")
            End Get
        End Property
    
        Public ReadOnly Property NomeSigla As String
            Get
                Return String.Format(
                    "{0} ({1})",
                    Value(Of String)("nome"),
                    Value(Of String)("sigla"))
            End Get
        End Property
    
    End Class
    

    This is nearly the same thing, except now the JRecord is a JObject so you can pass it around freely. The tradeoff is that now it has to copy all the properties when the JRecord is first constructed. We take advantage of JObject's built-in copy constructor to do this.