How to Programmatically Choose a Constructor During Deserialization

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



Leave a reply



Submit