How to programmatically choose a constructor during deserialization?
If it is not possible to add a [JsonConstructor]
attribute to the target class (because you don't own the code), then the usual workaround is to create a custom JsonConverter
as was suggested by @James Thorpe in the comments. It is pretty straightforward. You can load the JSON into a JObject
, then pick the individual properties out of it to instantiate your Claim
instance. Here is the code you would need:
class ClaimConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(System.Security.Claims.Claim));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string type = (string)jo["Type"];
string value = (string)jo["Value"];
string valueType = (string)jo["ValueType"];
string issuer = (string)jo["Issuer"];
string originalIssuer = (string)jo["OriginalIssuer"];
return new Claim(type, value, valueType, issuer, originalIssuer);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, simply pass an instance of it to the JsonConvert.DeserializeObject<T>()
method call:
Claim claim = JsonConvert.DeserializeObject<Claim>(json, new ClaimConverter());
Fiddle: https://dotnetfiddle.net/7LjgGR
How are constructors called during serialization and deserialization?
Example:
public class ParentDeserializationTest {
public static void main(String[] args){
try {
System.out.println("Creating...");
Child c = new Child(1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
c.field = 10;
System.out.println("Serializing...");
oos.writeObject(c);
oos.flush();
baos.flush();
oos.close();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
System.out.println("Deserializing...");
Child c1 = (Child)ois.readObject();
System.out.println("c1.i="+c1.getI());
System.out.println("c1.field="+c1.getField());
} catch (IOException ex){
ex.printStackTrace();
} catch (ClassNotFoundException ex){
ex.printStackTrace();
}
}
public static class Parent {
protected int field;
protected Parent(){
field = 5;
System.out.println("Parent::Constructor");
}
public int getField() {
return field;
}
}
public static class Child extends Parent implements Serializable{
protected int i;
public Child(int i){
this.i = i;
System.out.println("Child::Constructor");
}
public int getI() {
return i;
}
}
}
Output:
Creating...
Parent::Constructor
Child::Constructor
Serializing...
Deserializing...
Parent::Constructor
c1.i=1
c1.field=5
So if you deserialized your object, its constructors doesn't called, but default constructor of its parent will be called.
And don't forget: all your serializable object should have a standard constructor without parameters.
Newtonsoft.Json - how can I choose the constructor to deserialize based on passed object properties types?
There is no way to configure Json.NET to choose a constructor based on the presence or absence of certain properties in the JSON to be deserialized. It simply isn't implemented.
As a workaround, you can create a custom JsonConverter<Test<T, U>>
that deserializes to some intermediate DTO that tracks the presence of both properties, and then chooses the correct constructor afterwards. Then you can create a custom contract resolver that applies the converter to all concrete types Test<T, U>
.
The following converter and contract resolver perform this task:
class TestConverter<T, U> : JsonConverter where U : struct
{
// Here we make use of the {PropertyName}Specified pattern to track which properties actually got deserialized.
// https://stackoverflow.com/questions/39223335/how-to-force-newtonsoft-json-to-serialize-all-properties-strange-behavior-with/
class TestDTO
{
public T FirstProperty { get; set; }
[JsonIgnore] public bool FirstPropertySpecified { get; set; }
public U SecondProperty { get; set; }
[JsonIgnore] public bool SecondPropertySpecified { get; set; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<TestDTO>(reader);
if (dto == null)
return null;
else if (dto.FirstPropertySpecified && !dto.SecondPropertySpecified)
return new Test<T, U>(dto.FirstProperty);
else if (!dto.FirstPropertySpecified && dto.SecondPropertySpecified)
return new Test<T, U>(dto.SecondProperty);
else
throw new InvalidOperationException(string.Format("Wrong number of properties specified for {0}", objectType));
}
public override bool CanConvert(Type objectType) => objectType == typeof(Test<T, U>);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
public class TestContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Test<,>))
contract.Converter = (JsonConverter)Activator.CreateInstance(typeof(TestConverter<,>).MakeGenericType(objectType.GetGenericArguments()));
return contract;
}
}
Then use them e.g. as follows:
var json1 = @"{""FirstProperty"":""hello""}";
var json2 = @"{""SecondProperty"": 10101}";
IContractResolver resolver = new TestContractResolver(); // Cache this statically for best performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var test1 = JsonConvert.DeserializeObject<Test<string, long>>(json1, settings);
Assert.AreEqual(test1.FirstProperty, "hello");
var test2 = JsonConvert.DeserializeObject<Test<string, long>>(json2, settings);
Assert.AreEqual(test2.SecondProperty, 10101L);
Notes:
The converter throws an
InvalidOperationException
if the JSON does not contain exactly one of the two properties.Feel free to modify this as per your requirements.
The converter does not implement serialization as your
Test<T, U>
type does not provide a method to track which property was initialized.The converter does not attempt to handle subclasses of
Test<T, U>
.
Demo fiddle here.
C# Newtonsoft Deserialize Custom Object with Claims?
You will have to write a custom converter, there is an example for Claim class in this answer: https://stackoverflow.com/a/28155770/6881299
How to inject a logger into an object during deserialization via a constructor parameter
To inject a logger into the ConsolidatedPortfolio
object during deserialization you will need to create a custom JsonConverter
class for it:
class ConsolidatedPortfolioConverter : CustomCreationConverter<ConsolidatedPortfolio>
{
private StructuredLogger StructuredLogger { get; set; }
public ConsolidatedPortfolioConverter(StructuredLogger sl)
{
StructuredLogger = sl;
}
public override ConsolidatedPortfolio Create(Type objectType)
{
return new ConsolidatedPortfolio(StructuredLogger);
}
}
When you want to deserialize, construct the converter with the logger you want to use, then pass the converter to the DeserializeObject
method:
var converter = new ConsolidatedPortfolioConverter(structuredLogger);
var consolPortf = JsonConvert.DeserializeObject<ConsolidatedPortfolio>(consolPortJson, converter);
Here is a working demo: https://dotnetfiddle.net/OT324B
Related Topics
What Is Imex Within Oledb Connection Strings
How to Detect If Type Is Another Generic Type
Textrenderer.Measuretext and Graphics.Measurestring Mismatch in Size
Retry a Task Multiple Times Based on User Input in Case of an Exception in Task
Algorithm to Find Which Numbers from a List of Size N Sum to Another Number
Incremental JSON Parsing in C#
How to Call C# Dll Function from Vbscript
How to Update Textbox on Gui from Another Thread
Getting Individual Windows Application Current Volume Output Level as Visualized in Audio Mixer
What Is the Max Limit of Data into List<String> in C#
Should You Implement Idisposable.Dispose() So That It Never Throws
Ziparchive Creates Invalid Zip File
String Interning in .Net Framework - What Are the Benefits and When to Use Interning
C#: Should Object Variables Be Assigned to Null