Pass additional data to JsonConverter











up vote
1
down vote

favorite












I deserialize object where one of the properties is a foreign key (eg an identity value from a database table). During deserialization I would like to use a JsonConverter to retrieve the corresponding object from a collection.



I know how to write a use custom JsonConverters. I don't know how to pass the collection to the JsonConverter, because the converter is specified at design time (like below), but the collection obviously only exists at runtime:



  <JsonConverter(GetType(JSonCustomConverter))>
Public Property SomeProperty As SomePropertyClass


So the JSonCustomConverter's ReadJson should look this:



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return False
Dim value As String = reader.Value.ToString().Trim()
retun MagicallyGetMyCollectionValue(value)
End Function


So the silly function name MagicallyGetMyCollectionValue is just a placeholder to show you where I am stuck. I don't want to access the collection through a global variable, but I don't know how to pass the collection to the ReadJson either.



I would be happy, if someone could point me in the right direction.





Edit: Let me try to give a better example.



Suppose I have the following class:



class ParentObject
<JssonConverter(GetType(JsonCustomConverter))>
Property SomeProperty As SomePropertyClass
end class


I would deserialize my json data like this:



 dim result = JsonConvert.DeserializeObject(jsonData, GetType(ParentObject))


Now assume, that the json data doesn't contain the complete representation of an instance of the SomePropertyClass, but only a key value e.g. an key as string. Suppose I have a collection like this:



dim cache as Dictionary(of string, SomePropertyClass)


That cache shall contain all the instances that I need. So my JSonCustomConverter should have a ReadJson Function like this:



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return nothing
Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


So I want the ReadJson to lookup the instance based on the key value.



How would I pass the cache-Dictionary into the ReadJson function? I could use a singelton class that contains the cache an som getInstance-method to retrieve it, but I wouldn't want to do this.










share|improve this question
























  • @Amessihel: No, I don't think so, because the solution shows how deserialize to one class or another based on the json array. In my case there I know the class to deserialize my property to. I try to add a better example to the question, to make it clearer, what I want (that will take a few minutes).
    – Sascha
    Nov 7 at 16:36






  • 1




    Assuming you are constructing the serializer yourself, you can pass in additional data using StreamingContext.Context via JsonSerializer.Context. In JSON.NET how to get a reference to every deserialized object? shows an example.
    – dbc
    Nov 7 at 16:37










  • @dbc That looks promising. I will try it and leave a comment on how it worked.
    – Sascha
    Nov 7 at 17:14










  • if it does work, could ya do me a favor and post the solution as an answer?
    – doom87er
    Nov 7 at 17:21










  • @doom87er -- working on one now.
    – dbc
    Nov 7 at 19:10















up vote
1
down vote

favorite












I deserialize object where one of the properties is a foreign key (eg an identity value from a database table). During deserialization I would like to use a JsonConverter to retrieve the corresponding object from a collection.



I know how to write a use custom JsonConverters. I don't know how to pass the collection to the JsonConverter, because the converter is specified at design time (like below), but the collection obviously only exists at runtime:



  <JsonConverter(GetType(JSonCustomConverter))>
Public Property SomeProperty As SomePropertyClass


So the JSonCustomConverter's ReadJson should look this:



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return False
Dim value As String = reader.Value.ToString().Trim()
retun MagicallyGetMyCollectionValue(value)
End Function


So the silly function name MagicallyGetMyCollectionValue is just a placeholder to show you where I am stuck. I don't want to access the collection through a global variable, but I don't know how to pass the collection to the ReadJson either.



I would be happy, if someone could point me in the right direction.





Edit: Let me try to give a better example.



Suppose I have the following class:



class ParentObject
<JssonConverter(GetType(JsonCustomConverter))>
Property SomeProperty As SomePropertyClass
end class


I would deserialize my json data like this:



 dim result = JsonConvert.DeserializeObject(jsonData, GetType(ParentObject))


Now assume, that the json data doesn't contain the complete representation of an instance of the SomePropertyClass, but only a key value e.g. an key as string. Suppose I have a collection like this:



dim cache as Dictionary(of string, SomePropertyClass)


That cache shall contain all the instances that I need. So my JSonCustomConverter should have a ReadJson Function like this:



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return nothing
Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


So I want the ReadJson to lookup the instance based on the key value.



How would I pass the cache-Dictionary into the ReadJson function? I could use a singelton class that contains the cache an som getInstance-method to retrieve it, but I wouldn't want to do this.










share|improve this question
























  • @Amessihel: No, I don't think so, because the solution shows how deserialize to one class or another based on the json array. In my case there I know the class to deserialize my property to. I try to add a better example to the question, to make it clearer, what I want (that will take a few minutes).
    – Sascha
    Nov 7 at 16:36






  • 1




    Assuming you are constructing the serializer yourself, you can pass in additional data using StreamingContext.Context via JsonSerializer.Context. In JSON.NET how to get a reference to every deserialized object? shows an example.
    – dbc
    Nov 7 at 16:37










  • @dbc That looks promising. I will try it and leave a comment on how it worked.
    – Sascha
    Nov 7 at 17:14










  • if it does work, could ya do me a favor and post the solution as an answer?
    – doom87er
    Nov 7 at 17:21










  • @doom87er -- working on one now.
    – dbc
    Nov 7 at 19:10













up vote
1
down vote

favorite









up vote
1
down vote

favorite











I deserialize object where one of the properties is a foreign key (eg an identity value from a database table). During deserialization I would like to use a JsonConverter to retrieve the corresponding object from a collection.



I know how to write a use custom JsonConverters. I don't know how to pass the collection to the JsonConverter, because the converter is specified at design time (like below), but the collection obviously only exists at runtime:



  <JsonConverter(GetType(JSonCustomConverter))>
Public Property SomeProperty As SomePropertyClass


So the JSonCustomConverter's ReadJson should look this:



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return False
Dim value As String = reader.Value.ToString().Trim()
retun MagicallyGetMyCollectionValue(value)
End Function


So the silly function name MagicallyGetMyCollectionValue is just a placeholder to show you where I am stuck. I don't want to access the collection through a global variable, but I don't know how to pass the collection to the ReadJson either.



I would be happy, if someone could point me in the right direction.





Edit: Let me try to give a better example.



Suppose I have the following class:



class ParentObject
<JssonConverter(GetType(JsonCustomConverter))>
Property SomeProperty As SomePropertyClass
end class


I would deserialize my json data like this:



 dim result = JsonConvert.DeserializeObject(jsonData, GetType(ParentObject))


Now assume, that the json data doesn't contain the complete representation of an instance of the SomePropertyClass, but only a key value e.g. an key as string. Suppose I have a collection like this:



dim cache as Dictionary(of string, SomePropertyClass)


That cache shall contain all the instances that I need. So my JSonCustomConverter should have a ReadJson Function like this:



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return nothing
Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


So I want the ReadJson to lookup the instance based on the key value.



How would I pass the cache-Dictionary into the ReadJson function? I could use a singelton class that contains the cache an som getInstance-method to retrieve it, but I wouldn't want to do this.










share|improve this question















I deserialize object where one of the properties is a foreign key (eg an identity value from a database table). During deserialization I would like to use a JsonConverter to retrieve the corresponding object from a collection.



I know how to write a use custom JsonConverters. I don't know how to pass the collection to the JsonConverter, because the converter is specified at design time (like below), but the collection obviously only exists at runtime:



  <JsonConverter(GetType(JSonCustomConverter))>
Public Property SomeProperty As SomePropertyClass


So the JSonCustomConverter's ReadJson should look this:



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return False
Dim value As String = reader.Value.ToString().Trim()
retun MagicallyGetMyCollectionValue(value)
End Function


So the silly function name MagicallyGetMyCollectionValue is just a placeholder to show you where I am stuck. I don't want to access the collection through a global variable, but I don't know how to pass the collection to the ReadJson either.



I would be happy, if someone could point me in the right direction.





Edit: Let me try to give a better example.



Suppose I have the following class:



class ParentObject
<JssonConverter(GetType(JsonCustomConverter))>
Property SomeProperty As SomePropertyClass
end class


I would deserialize my json data like this:



 dim result = JsonConvert.DeserializeObject(jsonData, GetType(ParentObject))


Now assume, that the json data doesn't contain the complete representation of an instance of the SomePropertyClass, but only a key value e.g. an key as string. Suppose I have a collection like this:



dim cache as Dictionary(of string, SomePropertyClass)


That cache shall contain all the instances that I need. So my JSonCustomConverter should have a ReadJson Function like this:



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
If reader.Value Is Nothing Then Return nothing
Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


So I want the ReadJson to lookup the instance based on the key value.



How would I pass the cache-Dictionary into the ReadJson function? I could use a singelton class that contains the cache an som getInstance-method to retrieve it, but I wouldn't want to do this.







vb.net json.net






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 7 at 20:10









dbc

52.3k765116




52.3k765116










asked Nov 7 at 16:17









Sascha

91111127




91111127












  • @Amessihel: No, I don't think so, because the solution shows how deserialize to one class or another based on the json array. In my case there I know the class to deserialize my property to. I try to add a better example to the question, to make it clearer, what I want (that will take a few minutes).
    – Sascha
    Nov 7 at 16:36






  • 1




    Assuming you are constructing the serializer yourself, you can pass in additional data using StreamingContext.Context via JsonSerializer.Context. In JSON.NET how to get a reference to every deserialized object? shows an example.
    – dbc
    Nov 7 at 16:37










  • @dbc That looks promising. I will try it and leave a comment on how it worked.
    – Sascha
    Nov 7 at 17:14










  • if it does work, could ya do me a favor and post the solution as an answer?
    – doom87er
    Nov 7 at 17:21










  • @doom87er -- working on one now.
    – dbc
    Nov 7 at 19:10


















  • @Amessihel: No, I don't think so, because the solution shows how deserialize to one class or another based on the json array. In my case there I know the class to deserialize my property to. I try to add a better example to the question, to make it clearer, what I want (that will take a few minutes).
    – Sascha
    Nov 7 at 16:36






  • 1




    Assuming you are constructing the serializer yourself, you can pass in additional data using StreamingContext.Context via JsonSerializer.Context. In JSON.NET how to get a reference to every deserialized object? shows an example.
    – dbc
    Nov 7 at 16:37










  • @dbc That looks promising. I will try it and leave a comment on how it worked.
    – Sascha
    Nov 7 at 17:14










  • if it does work, could ya do me a favor and post the solution as an answer?
    – doom87er
    Nov 7 at 17:21










  • @doom87er -- working on one now.
    – dbc
    Nov 7 at 19:10
















@Amessihel: No, I don't think so, because the solution shows how deserialize to one class or another based on the json array. In my case there I know the class to deserialize my property to. I try to add a better example to the question, to make it clearer, what I want (that will take a few minutes).
– Sascha
Nov 7 at 16:36




@Amessihel: No, I don't think so, because the solution shows how deserialize to one class or another based on the json array. In my case there I know the class to deserialize my property to. I try to add a better example to the question, to make it clearer, what I want (that will take a few minutes).
– Sascha
Nov 7 at 16:36




1




1




Assuming you are constructing the serializer yourself, you can pass in additional data using StreamingContext.Context via JsonSerializer.Context. In JSON.NET how to get a reference to every deserialized object? shows an example.
– dbc
Nov 7 at 16:37




Assuming you are constructing the serializer yourself, you can pass in additional data using StreamingContext.Context via JsonSerializer.Context. In JSON.NET how to get a reference to every deserialized object? shows an example.
– dbc
Nov 7 at 16:37












@dbc That looks promising. I will try it and leave a comment on how it worked.
– Sascha
Nov 7 at 17:14




@dbc That looks promising. I will try it and leave a comment on how it worked.
– Sascha
Nov 7 at 17:14












if it does work, could ya do me a favor and post the solution as an answer?
– doom87er
Nov 7 at 17:21




if it does work, could ya do me a favor and post the solution as an answer?
– doom87er
Nov 7 at 17:21












@doom87er -- working on one now.
– dbc
Nov 7 at 19:10




@doom87er -- working on one now.
– dbc
Nov 7 at 19:10












2 Answers
2






active

oldest

votes

















up vote
1
down vote













As requested by @doom87er I will share the code, that worked for me. The solution is based on the comment by @dbc, with some changes. Please treat below code more like a conceptual code: I had to change some of the names and leave out some logik, that isn't required for this proof of concept. So there might be typos in it.



The main solution is to subclass the DefaultContractResolver and add the the cache-dictionary to that class. Something like this:



Public Class CacheContractResolver
Inherits DefaultContractResolver
Public Cache As Dictionary(of string, SomePropertyClass)

Public Sub New(preFilledCache As Dictionary(of string, SomePropertyClass)
Me.Cache = preFilledCache
End Sub
End Class


Then you pass the custom contract resolver using the JsonSerializerSettings like this:



Dim settings = New JsonSerializerSettings
settings.ContractResolver = New SupportControllerContractResolver(prefilledCache)
Dim result = JsonConvert.DeserializeObject(Of ParentObject)(jsonData, settings)


where prefilledCache is an instance of a dictionary containing the SomePropertyClass-objects.



The last step is to retrieve the cache in my JsonConverter's ReadJson function (that I attached to the SomeProperty as shown in the original post's sample code):



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim cacheResolver = TryCast(serializer.ContractResolver, CacheContractResolver)
if cacheResolver is nothing return nothing ' add some better null handling here

Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cacheResolver.Cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


I tried it and it seems to work.



So in a nut shell:




  1. Subclass the DefaultContractResolver and include all the additional data, that you need.

  2. Pass an instance of you custom contract resolver with the additional data in the JsonSerializerSettings.

  3. In your JsonConverter trycast the passed contract resolver back to your custom contract resolver and there you have your additional data.


I would be happy if you comment on any catches that I might miss, but I think this should be solution that I can live with.



Thanks for you coments and help.
Sascha






share|improve this answer























  • Thanks. I was having Stack overflow issues with some really complex converters. Hopefully I can sort something out with this
    – doom87er
    Nov 7 at 20:54


















up vote
1
down vote













You can pass additional data to your custom JsonConverter using StreamingContext.Context via JsonSerializer.Context. Using this mechanism it becomes possible to map class instances to names in a generic manner.



First, define the following interfaces and generic converter:



Public Interface ISerializationContext
Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean
End Interface

Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name as String = Nothing
if (Not nameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
nameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Next, define the following concrete implementations:



Public Class RootObject
<JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> _
Public Property SomeProperty As SomePropertyClass
End Class

Public Class SomePropertyClass
End Class

Public Class MySerializationContext : Implements ISerializationContext
Public Function Add(value as SomePropertyClass, name as String) as SomePropertyClass
Return SomePropertyNameTable.Add(value, name)
End Function

Property SomePropertyNameTable as NameTable(Of SomePropertyClass) = New NameTable(Of SomePropertyClass)

Public Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean Implements ISerializationContext.TryGetNameTable
if (GetType(T) Is GetType(SomePropertyClass))
table = SomePropertyNameTable
return True
End If

table = Nothing
return False
End Function
End Class


Now, you can replace instances of SomePropertyClass with their names during deserialization as follows:



Dim context as MySerializationContext = New MySerializationContext()
Dim someProperty as SomePropertyClass = context.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }
Dim settings = new JsonSerializerSettings With _
{ _
.Context = New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.All, context)
}
Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Notes:





  • ISerializationContext.TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) is generic so that object-to-name replacement can be supported for multiple types of objects simultaneously without the converters interfering with each other.



    The concrete implementation need not be so generic, however. Here MySerializationContext only supports name replacement for instances of SomePropertyClass. Others could be added as needed.



  • As stated in Does Json.NET cache types' serialization information?, Newtonsoft recommends caching instances of DefaultContractResolver and its subtypes for best performance. Thus it may be preferable to pass additional data via StreamingContext.Context rather than via freshly allocated instances of subclasses of DefaultContractResolver.



Sample working .Net fiddle #1 here.



As an alternative, while the design above works, in my opinion it would be simpler to remove <JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> from SomeProperty and instead pass an appropriately initialized ObjectToNameConverter(Of SomePropertyClass), containing a local reference to some INameTable(Of SomePropertyClass), in JsonSerializerSettings.Converters.



Define the converter and interfaces like so. Notice that ObjectToNameConverter(Of T) now has a parameterized constructor and that ISerializationContext is no longer required:



Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

Private Property NameTable as INameTable(Of T)

Public Sub New(nameTable as INameTable(Of T))
If nameTable Is Nothing
Throw new ArgumentNullException("nameTable")
End If
Me.NameTable = nameTable
End Sub

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)

Dim name as String = Nothing
if (Not NameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
NameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Then serialize as follows:



dim nameTable = new NameTable(Of SomePropertyClass)()
Dim someProperty as SomePropertyClass = nameTable.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }

Dim settings = new JsonSerializerSettings()
settings.Converters.Add(new ObjectToNameConverter(Of SomePropertyClass)(nameTable))

Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Doing things this way eliminates the dependence of static serialization methods on runtime code that is present in the first solution. Now all name replacement logic is handled in runtime in one location.



Sample fiddle #2 here.






share|improve this answer



















  • 1




    @Sascha - answer updated.
    – dbc
    Nov 7 at 20:11











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














 

draft saved


draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53193503%2fpass-additional-data-to-jsonconverter%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
1
down vote













As requested by @doom87er I will share the code, that worked for me. The solution is based on the comment by @dbc, with some changes. Please treat below code more like a conceptual code: I had to change some of the names and leave out some logik, that isn't required for this proof of concept. So there might be typos in it.



The main solution is to subclass the DefaultContractResolver and add the the cache-dictionary to that class. Something like this:



Public Class CacheContractResolver
Inherits DefaultContractResolver
Public Cache As Dictionary(of string, SomePropertyClass)

Public Sub New(preFilledCache As Dictionary(of string, SomePropertyClass)
Me.Cache = preFilledCache
End Sub
End Class


Then you pass the custom contract resolver using the JsonSerializerSettings like this:



Dim settings = New JsonSerializerSettings
settings.ContractResolver = New SupportControllerContractResolver(prefilledCache)
Dim result = JsonConvert.DeserializeObject(Of ParentObject)(jsonData, settings)


where prefilledCache is an instance of a dictionary containing the SomePropertyClass-objects.



The last step is to retrieve the cache in my JsonConverter's ReadJson function (that I attached to the SomeProperty as shown in the original post's sample code):



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim cacheResolver = TryCast(serializer.ContractResolver, CacheContractResolver)
if cacheResolver is nothing return nothing ' add some better null handling here

Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cacheResolver.Cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


I tried it and it seems to work.



So in a nut shell:




  1. Subclass the DefaultContractResolver and include all the additional data, that you need.

  2. Pass an instance of you custom contract resolver with the additional data in the JsonSerializerSettings.

  3. In your JsonConverter trycast the passed contract resolver back to your custom contract resolver and there you have your additional data.


I would be happy if you comment on any catches that I might miss, but I think this should be solution that I can live with.



Thanks for you coments and help.
Sascha






share|improve this answer























  • Thanks. I was having Stack overflow issues with some really complex converters. Hopefully I can sort something out with this
    – doom87er
    Nov 7 at 20:54















up vote
1
down vote













As requested by @doom87er I will share the code, that worked for me. The solution is based on the comment by @dbc, with some changes. Please treat below code more like a conceptual code: I had to change some of the names and leave out some logik, that isn't required for this proof of concept. So there might be typos in it.



The main solution is to subclass the DefaultContractResolver and add the the cache-dictionary to that class. Something like this:



Public Class CacheContractResolver
Inherits DefaultContractResolver
Public Cache As Dictionary(of string, SomePropertyClass)

Public Sub New(preFilledCache As Dictionary(of string, SomePropertyClass)
Me.Cache = preFilledCache
End Sub
End Class


Then you pass the custom contract resolver using the JsonSerializerSettings like this:



Dim settings = New JsonSerializerSettings
settings.ContractResolver = New SupportControllerContractResolver(prefilledCache)
Dim result = JsonConvert.DeserializeObject(Of ParentObject)(jsonData, settings)


where prefilledCache is an instance of a dictionary containing the SomePropertyClass-objects.



The last step is to retrieve the cache in my JsonConverter's ReadJson function (that I attached to the SomeProperty as shown in the original post's sample code):



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim cacheResolver = TryCast(serializer.ContractResolver, CacheContractResolver)
if cacheResolver is nothing return nothing ' add some better null handling here

Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cacheResolver.Cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


I tried it and it seems to work.



So in a nut shell:




  1. Subclass the DefaultContractResolver and include all the additional data, that you need.

  2. Pass an instance of you custom contract resolver with the additional data in the JsonSerializerSettings.

  3. In your JsonConverter trycast the passed contract resolver back to your custom contract resolver and there you have your additional data.


I would be happy if you comment on any catches that I might miss, but I think this should be solution that I can live with.



Thanks for you coments and help.
Sascha






share|improve this answer























  • Thanks. I was having Stack overflow issues with some really complex converters. Hopefully I can sort something out with this
    – doom87er
    Nov 7 at 20:54













up vote
1
down vote










up vote
1
down vote









As requested by @doom87er I will share the code, that worked for me. The solution is based on the comment by @dbc, with some changes. Please treat below code more like a conceptual code: I had to change some of the names and leave out some logik, that isn't required for this proof of concept. So there might be typos in it.



The main solution is to subclass the DefaultContractResolver and add the the cache-dictionary to that class. Something like this:



Public Class CacheContractResolver
Inherits DefaultContractResolver
Public Cache As Dictionary(of string, SomePropertyClass)

Public Sub New(preFilledCache As Dictionary(of string, SomePropertyClass)
Me.Cache = preFilledCache
End Sub
End Class


Then you pass the custom contract resolver using the JsonSerializerSettings like this:



Dim settings = New JsonSerializerSettings
settings.ContractResolver = New SupportControllerContractResolver(prefilledCache)
Dim result = JsonConvert.DeserializeObject(Of ParentObject)(jsonData, settings)


where prefilledCache is an instance of a dictionary containing the SomePropertyClass-objects.



The last step is to retrieve the cache in my JsonConverter's ReadJson function (that I attached to the SomeProperty as shown in the original post's sample code):



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim cacheResolver = TryCast(serializer.ContractResolver, CacheContractResolver)
if cacheResolver is nothing return nothing ' add some better null handling here

Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cacheResolver.Cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


I tried it and it seems to work.



So in a nut shell:




  1. Subclass the DefaultContractResolver and include all the additional data, that you need.

  2. Pass an instance of you custom contract resolver with the additional data in the JsonSerializerSettings.

  3. In your JsonConverter trycast the passed contract resolver back to your custom contract resolver and there you have your additional data.


I would be happy if you comment on any catches that I might miss, but I think this should be solution that I can live with.



Thanks for you coments and help.
Sascha






share|improve this answer














As requested by @doom87er I will share the code, that worked for me. The solution is based on the comment by @dbc, with some changes. Please treat below code more like a conceptual code: I had to change some of the names and leave out some logik, that isn't required for this proof of concept. So there might be typos in it.



The main solution is to subclass the DefaultContractResolver and add the the cache-dictionary to that class. Something like this:



Public Class CacheContractResolver
Inherits DefaultContractResolver
Public Cache As Dictionary(of string, SomePropertyClass)

Public Sub New(preFilledCache As Dictionary(of string, SomePropertyClass)
Me.Cache = preFilledCache
End Sub
End Class


Then you pass the custom contract resolver using the JsonSerializerSettings like this:



Dim settings = New JsonSerializerSettings
settings.ContractResolver = New SupportControllerContractResolver(prefilledCache)
Dim result = JsonConvert.DeserializeObject(Of ParentObject)(jsonData, settings)


where prefilledCache is an instance of a dictionary containing the SomePropertyClass-objects.



The last step is to retrieve the cache in my JsonConverter's ReadJson function (that I attached to the SomeProperty as shown in the original post's sample code):



Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim cacheResolver = TryCast(serializer.ContractResolver, CacheContractResolver)
if cacheResolver is nothing return nothing ' add some better null handling here

Dim value As String = reader.Value.ToString().Trim()
Dim cachedObject as SomePropertyClass = nothing
if cacheResolver.Cache.TryGetValue(value, cachedObject) then return cachedObject
retun Nothing ' or new SomePropertyClass(value)
End Function


I tried it and it seems to work.



So in a nut shell:




  1. Subclass the DefaultContractResolver and include all the additional data, that you need.

  2. Pass an instance of you custom contract resolver with the additional data in the JsonSerializerSettings.

  3. In your JsonConverter trycast the passed contract resolver back to your custom contract resolver and there you have your additional data.


I would be happy if you comment on any catches that I might miss, but I think this should be solution that I can live with.



Thanks for you coments and help.
Sascha







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 7 at 18:56

























answered Nov 7 at 18:19









Sascha

91111127




91111127












  • Thanks. I was having Stack overflow issues with some really complex converters. Hopefully I can sort something out with this
    – doom87er
    Nov 7 at 20:54


















  • Thanks. I was having Stack overflow issues with some really complex converters. Hopefully I can sort something out with this
    – doom87er
    Nov 7 at 20:54
















Thanks. I was having Stack overflow issues with some really complex converters. Hopefully I can sort something out with this
– doom87er
Nov 7 at 20:54




Thanks. I was having Stack overflow issues with some really complex converters. Hopefully I can sort something out with this
– doom87er
Nov 7 at 20:54












up vote
1
down vote













You can pass additional data to your custom JsonConverter using StreamingContext.Context via JsonSerializer.Context. Using this mechanism it becomes possible to map class instances to names in a generic manner.



First, define the following interfaces and generic converter:



Public Interface ISerializationContext
Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean
End Interface

Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name as String = Nothing
if (Not nameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
nameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Next, define the following concrete implementations:



Public Class RootObject
<JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> _
Public Property SomeProperty As SomePropertyClass
End Class

Public Class SomePropertyClass
End Class

Public Class MySerializationContext : Implements ISerializationContext
Public Function Add(value as SomePropertyClass, name as String) as SomePropertyClass
Return SomePropertyNameTable.Add(value, name)
End Function

Property SomePropertyNameTable as NameTable(Of SomePropertyClass) = New NameTable(Of SomePropertyClass)

Public Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean Implements ISerializationContext.TryGetNameTable
if (GetType(T) Is GetType(SomePropertyClass))
table = SomePropertyNameTable
return True
End If

table = Nothing
return False
End Function
End Class


Now, you can replace instances of SomePropertyClass with their names during deserialization as follows:



Dim context as MySerializationContext = New MySerializationContext()
Dim someProperty as SomePropertyClass = context.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }
Dim settings = new JsonSerializerSettings With _
{ _
.Context = New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.All, context)
}
Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Notes:





  • ISerializationContext.TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) is generic so that object-to-name replacement can be supported for multiple types of objects simultaneously without the converters interfering with each other.



    The concrete implementation need not be so generic, however. Here MySerializationContext only supports name replacement for instances of SomePropertyClass. Others could be added as needed.



  • As stated in Does Json.NET cache types' serialization information?, Newtonsoft recommends caching instances of DefaultContractResolver and its subtypes for best performance. Thus it may be preferable to pass additional data via StreamingContext.Context rather than via freshly allocated instances of subclasses of DefaultContractResolver.



Sample working .Net fiddle #1 here.



As an alternative, while the design above works, in my opinion it would be simpler to remove <JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> from SomeProperty and instead pass an appropriately initialized ObjectToNameConverter(Of SomePropertyClass), containing a local reference to some INameTable(Of SomePropertyClass), in JsonSerializerSettings.Converters.



Define the converter and interfaces like so. Notice that ObjectToNameConverter(Of T) now has a parameterized constructor and that ISerializationContext is no longer required:



Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

Private Property NameTable as INameTable(Of T)

Public Sub New(nameTable as INameTable(Of T))
If nameTable Is Nothing
Throw new ArgumentNullException("nameTable")
End If
Me.NameTable = nameTable
End Sub

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)

Dim name as String = Nothing
if (Not NameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
NameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Then serialize as follows:



dim nameTable = new NameTable(Of SomePropertyClass)()
Dim someProperty as SomePropertyClass = nameTable.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }

Dim settings = new JsonSerializerSettings()
settings.Converters.Add(new ObjectToNameConverter(Of SomePropertyClass)(nameTable))

Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Doing things this way eliminates the dependence of static serialization methods on runtime code that is present in the first solution. Now all name replacement logic is handled in runtime in one location.



Sample fiddle #2 here.






share|improve this answer



















  • 1




    @Sascha - answer updated.
    – dbc
    Nov 7 at 20:11















up vote
1
down vote













You can pass additional data to your custom JsonConverter using StreamingContext.Context via JsonSerializer.Context. Using this mechanism it becomes possible to map class instances to names in a generic manner.



First, define the following interfaces and generic converter:



Public Interface ISerializationContext
Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean
End Interface

Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name as String = Nothing
if (Not nameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
nameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Next, define the following concrete implementations:



Public Class RootObject
<JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> _
Public Property SomeProperty As SomePropertyClass
End Class

Public Class SomePropertyClass
End Class

Public Class MySerializationContext : Implements ISerializationContext
Public Function Add(value as SomePropertyClass, name as String) as SomePropertyClass
Return SomePropertyNameTable.Add(value, name)
End Function

Property SomePropertyNameTable as NameTable(Of SomePropertyClass) = New NameTable(Of SomePropertyClass)

Public Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean Implements ISerializationContext.TryGetNameTable
if (GetType(T) Is GetType(SomePropertyClass))
table = SomePropertyNameTable
return True
End If

table = Nothing
return False
End Function
End Class


Now, you can replace instances of SomePropertyClass with their names during deserialization as follows:



Dim context as MySerializationContext = New MySerializationContext()
Dim someProperty as SomePropertyClass = context.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }
Dim settings = new JsonSerializerSettings With _
{ _
.Context = New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.All, context)
}
Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Notes:





  • ISerializationContext.TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) is generic so that object-to-name replacement can be supported for multiple types of objects simultaneously without the converters interfering with each other.



    The concrete implementation need not be so generic, however. Here MySerializationContext only supports name replacement for instances of SomePropertyClass. Others could be added as needed.



  • As stated in Does Json.NET cache types' serialization information?, Newtonsoft recommends caching instances of DefaultContractResolver and its subtypes for best performance. Thus it may be preferable to pass additional data via StreamingContext.Context rather than via freshly allocated instances of subclasses of DefaultContractResolver.



Sample working .Net fiddle #1 here.



As an alternative, while the design above works, in my opinion it would be simpler to remove <JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> from SomeProperty and instead pass an appropriately initialized ObjectToNameConverter(Of SomePropertyClass), containing a local reference to some INameTable(Of SomePropertyClass), in JsonSerializerSettings.Converters.



Define the converter and interfaces like so. Notice that ObjectToNameConverter(Of T) now has a parameterized constructor and that ISerializationContext is no longer required:



Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

Private Property NameTable as INameTable(Of T)

Public Sub New(nameTable as INameTable(Of T))
If nameTable Is Nothing
Throw new ArgumentNullException("nameTable")
End If
Me.NameTable = nameTable
End Sub

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)

Dim name as String = Nothing
if (Not NameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
NameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Then serialize as follows:



dim nameTable = new NameTable(Of SomePropertyClass)()
Dim someProperty as SomePropertyClass = nameTable.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }

Dim settings = new JsonSerializerSettings()
settings.Converters.Add(new ObjectToNameConverter(Of SomePropertyClass)(nameTable))

Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Doing things this way eliminates the dependence of static serialization methods on runtime code that is present in the first solution. Now all name replacement logic is handled in runtime in one location.



Sample fiddle #2 here.






share|improve this answer



















  • 1




    @Sascha - answer updated.
    – dbc
    Nov 7 at 20:11













up vote
1
down vote










up vote
1
down vote









You can pass additional data to your custom JsonConverter using StreamingContext.Context via JsonSerializer.Context. Using this mechanism it becomes possible to map class instances to names in a generic manner.



First, define the following interfaces and generic converter:



Public Interface ISerializationContext
Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean
End Interface

Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name as String = Nothing
if (Not nameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
nameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Next, define the following concrete implementations:



Public Class RootObject
<JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> _
Public Property SomeProperty As SomePropertyClass
End Class

Public Class SomePropertyClass
End Class

Public Class MySerializationContext : Implements ISerializationContext
Public Function Add(value as SomePropertyClass, name as String) as SomePropertyClass
Return SomePropertyNameTable.Add(value, name)
End Function

Property SomePropertyNameTable as NameTable(Of SomePropertyClass) = New NameTable(Of SomePropertyClass)

Public Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean Implements ISerializationContext.TryGetNameTable
if (GetType(T) Is GetType(SomePropertyClass))
table = SomePropertyNameTable
return True
End If

table = Nothing
return False
End Function
End Class


Now, you can replace instances of SomePropertyClass with their names during deserialization as follows:



Dim context as MySerializationContext = New MySerializationContext()
Dim someProperty as SomePropertyClass = context.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }
Dim settings = new JsonSerializerSettings With _
{ _
.Context = New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.All, context)
}
Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Notes:





  • ISerializationContext.TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) is generic so that object-to-name replacement can be supported for multiple types of objects simultaneously without the converters interfering with each other.



    The concrete implementation need not be so generic, however. Here MySerializationContext only supports name replacement for instances of SomePropertyClass. Others could be added as needed.



  • As stated in Does Json.NET cache types' serialization information?, Newtonsoft recommends caching instances of DefaultContractResolver and its subtypes for best performance. Thus it may be preferable to pass additional data via StreamingContext.Context rather than via freshly allocated instances of subclasses of DefaultContractResolver.



Sample working .Net fiddle #1 here.



As an alternative, while the design above works, in my opinion it would be simpler to remove <JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> from SomeProperty and instead pass an appropriately initialized ObjectToNameConverter(Of SomePropertyClass), containing a local reference to some INameTable(Of SomePropertyClass), in JsonSerializerSettings.Converters.



Define the converter and interfaces like so. Notice that ObjectToNameConverter(Of T) now has a parameterized constructor and that ISerializationContext is no longer required:



Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

Private Property NameTable as INameTable(Of T)

Public Sub New(nameTable as INameTable(Of T))
If nameTable Is Nothing
Throw new ArgumentNullException("nameTable")
End If
Me.NameTable = nameTable
End Sub

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)

Dim name as String = Nothing
if (Not NameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
NameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Then serialize as follows:



dim nameTable = new NameTable(Of SomePropertyClass)()
Dim someProperty as SomePropertyClass = nameTable.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }

Dim settings = new JsonSerializerSettings()
settings.Converters.Add(new ObjectToNameConverter(Of SomePropertyClass)(nameTable))

Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Doing things this way eliminates the dependence of static serialization methods on runtime code that is present in the first solution. Now all name replacement logic is handled in runtime in one location.



Sample fiddle #2 here.






share|improve this answer














You can pass additional data to your custom JsonConverter using StreamingContext.Context via JsonSerializer.Context. Using this mechanism it becomes possible to map class instances to names in a generic manner.



First, define the following interfaces and generic converter:



Public Interface ISerializationContext
Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean
End Interface

Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name as String = Nothing
if (Not nameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim context as ISerializationContext = CType(serializer.Context.Context, ISerializationContext)
If context Is Nothing
Throw New JsonSerializationException("No ISerializationContext.")
End If

Dim nameTable as INameTable(Of T) = Nothing
If (Not context.TryGetNameTable(Of T)(nameTable))
Throw New JsonSerializationException("No NameTable.")
End If

Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
nameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Next, define the following concrete implementations:



Public Class RootObject
<JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> _
Public Property SomeProperty As SomePropertyClass
End Class

Public Class SomePropertyClass
End Class

Public Class MySerializationContext : Implements ISerializationContext
Public Function Add(value as SomePropertyClass, name as String) as SomePropertyClass
Return SomePropertyNameTable.Add(value, name)
End Function

Property SomePropertyNameTable as NameTable(Of SomePropertyClass) = New NameTable(Of SomePropertyClass)

Public Function TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) as Boolean Implements ISerializationContext.TryGetNameTable
if (GetType(T) Is GetType(SomePropertyClass))
table = SomePropertyNameTable
return True
End If

table = Nothing
return False
End Function
End Class


Now, you can replace instances of SomePropertyClass with their names during deserialization as follows:



Dim context as MySerializationContext = New MySerializationContext()
Dim someProperty as SomePropertyClass = context.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }
Dim settings = new JsonSerializerSettings With _
{ _
.Context = New System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.All, context)
}
Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Notes:





  • ISerializationContext.TryGetNameTable(Of T)(ByRef table as INameTable(Of T)) is generic so that object-to-name replacement can be supported for multiple types of objects simultaneously without the converters interfering with each other.



    The concrete implementation need not be so generic, however. Here MySerializationContext only supports name replacement for instances of SomePropertyClass. Others could be added as needed.



  • As stated in Does Json.NET cache types' serialization information?, Newtonsoft recommends caching instances of DefaultContractResolver and its subtypes for best performance. Thus it may be preferable to pass additional data via StreamingContext.Context rather than via freshly allocated instances of subclasses of DefaultContractResolver.



Sample working .Net fiddle #1 here.



As an alternative, while the design above works, in my opinion it would be simpler to remove <JsonConverter(GetType(ObjectToNameConverter(Of SomePropertyClass)))> from SomeProperty and instead pass an appropriately initialized ObjectToNameConverter(Of SomePropertyClass), containing a local reference to some INameTable(Of SomePropertyClass), in JsonSerializerSettings.Converters.



Define the converter and interfaces like so. Notice that ObjectToNameConverter(Of T) now has a parameterized constructor and that ISerializationContext is no longer required:



Public Interface INameTable(Of T)
Function TryGetName(value As T, ByRef name as String) As Boolean
Function TryGetValue(name as String, ByRef value as T) As Boolean
End Interface

Public Class NameTable(Of T) : Implements INameTable(Of T)
Public Property Dictionary as Dictionary(Of String, T) = New Dictionary(Of String, T)()

Public Property ReverseDictionary as Dictionary(Of T, String) = New Dictionary(Of T, String)()

Public Function Add(value as T, name as String) as T
Dictionary.Add(name, value)
ReverseDictionary.Add(value, name)
Return value
End Function

Public Function TryGetName(value As T, ByRef name as String) As Boolean Implements INameTable(Of T).TryGetName
Return ReverseDictionary.TryGetValue(value, name)
End Function

Function TryGetValue(name as String, ByRef value as T) As Boolean Implements INameTable(Of T).TryGetValue
Return Dictionary.TryGetValue(name, value)
End Function
End Class

Public Class ObjectToNameConverter(Of T)
Inherits JsonConverter

Private Property NameTable as INameTable(Of T)

Public Sub New(nameTable as INameTable(Of T))
If nameTable Is Nothing
Throw new ArgumentNullException("nameTable")
End If
Me.NameTable = nameTable
End Sub

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

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Dim tValue = CType(value, T)

Dim name as String = Nothing
if (Not NameTable.TryGetName(tValue, name))
Throw New JsonSerializationException("No Name.")
End If

writer.WriteValue(name)
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim name As String = serializer.Deserialize(Of String)(reader)
If name Is Nothing Then
Return Nothing
End If

dim tValue as T = Nothing
NameTable.TryGetValue(name, tValue)
return tValue
End Function
End Class


Then serialize as follows:



dim nameTable = new NameTable(Of SomePropertyClass)()
Dim someProperty as SomePropertyClass = nameTable.Add(New SomePropertyClass(), "My Name")
Dim root as RootObject = New RootObject With { .SomeProperty = someProperty }

Dim settings = new JsonSerializerSettings()
settings.Converters.Add(new ObjectToNameConverter(Of SomePropertyClass)(nameTable))

Dim json as String = JsonConvert.SerializeObject(root, settings)
Console.WriteLine(json) ' Prints {"SomeProperty":"My Name"}
dim root2 as RootObject = JsonConvert.DeserializeObject(Of RootObject)(json, settings)
' Assert that the same instance of SomeProperty was used during deserialization
Assert.IsTrue(root2.SomeProperty Is root.SomeProperty)
Assert.IsTrue(json.Equals("{""SomeProperty"":""My Name""}"))


Doing things this way eliminates the dependence of static serialization methods on runtime code that is present in the first solution. Now all name replacement logic is handled in runtime in one location.



Sample fiddle #2 here.







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 7 at 20:09

























answered Nov 7 at 19:51









dbc

52.3k765116




52.3k765116








  • 1




    @Sascha - answer updated.
    – dbc
    Nov 7 at 20:11














  • 1




    @Sascha - answer updated.
    – dbc
    Nov 7 at 20:11








1




1




@Sascha - answer updated.
– dbc
Nov 7 at 20:11




@Sascha - answer updated.
– dbc
Nov 7 at 20:11


















 

draft saved


draft discarded



















































 


draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53193503%2fpass-additional-data-to-jsonconverter%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







這個網誌中的熱門文章

Tangent Lines Diagram Along Smooth Curve

Yusuf al-Mu'taman ibn Hud

Zucchini