I am trying to JSON serialize and deserialize the class PlanningData to a database. Serialization seems to work but when deserializing Dictionary<SomeEnum, MyInterface> always returns null even though I can correctly navigate the Dictionary keys and values in the Visual Studio JSON viewer when deserializing.
I am using Net 8 so I tried implementing this: System.Text.Json .NET 8 Polymorphic deserialization
and dbc's answer here: Is polymorphic deserialization possible in System.Text.Json?
but I could not get it to work, i.e. deserialization still returned null.
This is the code to set up serialization for the database:
modelBuilder.Entity<SomeEntity>()
.Property(c => c.PlanningData)
.HasColumnName("PlanningData")
.HasConversion(
v => JsonHelper.Serialize(v),
v => JsonHelper.Deserialize<PlanningData>(v)
);
public class PlanningData
{
public required ProfitLossData ProfitLossData { get; init; }
}
public class ProfitLossData
{
// Deserialization of this property always returns null
public Dictionary<string, List<ProfitLossDataLineItem>> ProfitLossDataLineItems { get; private set;}
}
public class ProfitLossDataLineItem : DataLineItem
{
}
public class DataLineItem
{
[System.Text.Json.Serialization.JsonConverter(typeof(PlanningCalculatedResultConverter))]
public Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption> AllPlanningCellValueObjects { get; set; } = new Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption>
{
{ PlanningType.A, new Class1ThatImplementsInterface() },
{ PlanningType.B, new Class2ThatImplementsInterface() },
{ PlanningType.C, new Class3ThatImplementsInterface() },
{ PlanningType.D, new Class4ThatImplementsInterface() },
{ PlanningType.E, new Class5ThatImplementsInterface()},
};
}
public static class JsonHelper
{
static readonly JsonSerializerOptions _options = new()
{
Converters = { new PlanningCalculatedResultConverter(), new NullableObjectArrayConverter() }
};
public static string Serialize<T>(T value)
{
return System.Text.Json.JsonSerializer.Serialize(value);
}
public static T Deserialize<T>(string json)
{
// this never calls my PlanningCalculatedResultConverter
var deserializedValue = System.Text.Json.JsonSerializer.Deserialize<T>(json, _options) ?? throw new InvalidOperationException("Deserialization returned null.");
return deserializedValue;
}
}
public class PlanningCalculatedResultConverter : JsonConverter<Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption>>
{
public override Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Deserialize the JSON to a JsonDocument
using (JsonDocument doc = JsonDocument.ParseValue(ref reader))
{
var dictionary = new Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption>();
// Iterate over each property in the JSON document
foreach (var property in doc.RootElement.EnumerateObject())
{
// Parse the PlanningType from the property name
if (Enum.TryParse<PlanningType>(property.Name, out var planningType))
{
// Deserialize the value to the appropriate type
var value = DeserializePlanningCalculatedResult(property.Value, planningType, options);
// Add the deserialized value to the dictionary
dictionary.Add(planningType, value);
}
else
{
throw new JsonException($"Unsupported PlanningType: {property.Name}");
}
}
return dictionary;
}
}
private IPlanningCalculatedResultAndRequiredInputDataForSingleOption DeserializePlanningCalculatedResult(JsonElement element, PlanningType planningType, JsonSerializerOptions options)
{
switch (planningType)
{
case PlanningType.A:
return JsonSerializer.Deserialize<Class1ThatImplementsInterface>(element.GetRawText(), options);
case PlanningType.B:
return JsonSerializer.Deserialize<Class2ThatImplementsInterface>(element.GetRawText(), options);
case PlanningType.C:
return JsonSerializer.Deserialize<Class3ThatImplementsInterface>(element.GetRawText(), options);
case PlanningType.D:
return JsonSerializer.Deserialize<Class4ThatImplementsInterface>(element.GetRawText(), options);
case PlanningType.E:
return JsonSerializer.Deserialize<Class5ThatImplementsInterfaceatedResult>(element.GetRawText(), options);
default:
throw new JsonException($"Unsupported PlanningType: {planningType}");
}
}
public override void Write(Utf8JsonWriter writer, Dictionary<PlanningType, IPlanningCalculatedResultAndRequiredInputDataForSingleOption> value, JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach (var kvp in value)
{
writer.WritePropertyName(kvp.Key.ToString());
JsonSerializer.Serialize(writer, kvp.Value, kvp.Value.GetType(), options);
}
writer.WriteEndObject();
}
}
public interface IPlanningCalculatedResultAndRequiredInputDataForSingleOption
{
public bool IsOptionInDefaultState => InputParameters.Values.All(x => x.IsParameterInDefaultState);
public double?[] CalculatedOptionResult { get; set; }
public PlanningType PlanningType { get; }
public Dictionary<InputParameter, ISingleInputParameter> InputParameters { get; }
}
public interface ISingleInputParameter
{
public bool IsParameterInDefaultState => InputValues.Length == 0;
public double[] InputValues { get; set; }
public string InputParameterName { get; set; }
public bool IsPercentageData { get; set; }
}