1

I need to serialize and deserialize a List(of T) via JSON.Net, where T is an object which contains a reference which cannot be serialized. Here is a simplified version:

Class MyObject

    Private ReadOnly _Parent As Word.Document

    Property Foo As String
    Property Bar As String

    Sub New(Parent As Word.Document, Foo As String, Bar As String)
        Me.New(Parent)
        Me.Foo = Foo
        Me.Bar = Bar
    End Sub

    Sub New(Parent As Word.Document)
        _Parent = Parent
    End Sub

    <JsonConstructor>
    Private Sub New()
    End Sub

    Function GetFile() As System.IO.FileInfo
        Return New FileInfo(_Parent.FullName)
    End Function

End Class

For the story, I store the JSON string (serialized list) inside a Word document variable. When I open the document, I take the string, deserialize it, and then I would like to be able to set the _Parent field to refer to the same document. The difficulty is not in knowing what _Parent should reference to, but to set the reference. Note I want to keep it Private, however it could be read/write if necessary.

Is there a way to tell JSON.Net to use the New(Parent As Word.Document) constructor, and to pass this Parent argument via JsonConvert.DeserializeObject(Of T)? Or at least to tell JSON.Net I want to run a specific Sub before/after deserializing?

An easy bypass is be to have the constructor below, but I dot not like it as it may get messed up if several documents are opened at the same time.

<JsonConstructor>
Private Sub New()
    _Parent = ThisWordApp.ActiveDocument
End Sub

I'm fine with responses in C#.

dbc
  • 104,963
  • 20
  • 228
  • 340
Ama
  • 1,373
  • 10
  • 24
  • `Word.Document` is a reference which is created by the Word COM model, when a document is open. Not sure what you mean by "modify", but for a given document, if I close and reopen it, the `Word.Document` reference would be different. Yes, a `Collection(Of T)` is fine. – Ama May 25 '19 at 21:02
  • The idea is I have a document I work on, I do some things which create some MyObjects. I save the document and close it (the list/collection of these objects is serialized into the document). On another day I open the document and the list/collection of MyObjects is available. – Ama May 25 '19 at 21:04
  • Following the article you mentioned, looks like I could use the `` attribute, but still I would be unable to pass the `Word.Document` object as an argument of the Sub to run `OnDeserialized`. https://www.newtonsoft.com/json/help/html/SerializationCallbacks.htm – Ama May 25 '19 at 21:13
  • Word.Document cannot be modified. https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.interop.word.document – Ama May 25 '19 at 21:19
  • You might consider the approach from [Pass additional data to JsonConverter](https://stackoverflow.com/a/53196799/3744182). – dbc May 25 '19 at 23:20

1 Answers1

2

You could adopt the second approach from this answer to Pass additional data to JsonConverter and create a CustomCreationConverter(Of MyObject) that allocates an instance of MyObject using a Word.Document passed into the converter itself.

First, define the following converter:

Class MyObjectConverter
    Inherits CustomCreationConverter(Of MyObject)

    Private ReadOnly _Parent As Word.Document           

    Sub New(Parent As Word.Document)
        If Parent Is Nothing Then
            Throw New ArgumentNullException("Parent")
        End If
        _Parent = Parent
    End Sub

    Overrides Function Create(objectType as Type) As MyObject
        Return New MyObject(_Parent)
    End Function
End Class

Then you can use it as follows:

Dim settings = New JsonSerializerSettings() With { .Converters = { new MyObjectConverter(document) } }
Dim list = JsonConvert.DeserializeObject(Of List(Of MyObject))(jsonString, settings)

Notes:

  • This solution has the added advantage that you no longer need the <JsonConstructor> Private Sub New() constructor for MyObject and can completely remove it.

  • This converter would never be applied at compile time using JsonConverterAttribute, it should only be constructed in runtime given a known Word.Document (the document variable in the code sample above).

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    That looks good! Unable to test it right now but I think that is what I needed. I really like the added advantage that there is no need to include JSON Attributes in the serializable class. – Ama May 26 '19 at 11:26
  • @Jimi - related question asked and answered here: [How can I deserialize instances of a type that has read-only back-references to some container type also being deserialized?](https://stackoverflow.com/q/56316631/3744182). An on-point question title should make it more easily searchable. – dbc May 26 '19 at 19:16