I am trying to deserialize a complex nested json to my object. The only thing is that in my object I use abstract types, so it must have some logic to use the correct derived class.
The types are saved in enums.
To explain it I will keep it fairly simple and do we only nest once, but for my purpose it is more nested with objects and types.
{
"screen":{
"type":"Component",
"footer":{
"type":"Bar"
},
"header":{
"type":"Top"
}
}
}
public abstract class Screen
{
public abstract ScreenType Type { get; }
}
public enum ScreenType
{
Component,
b,
c,
d,
e
}
public sealed class ComponentScreen : Screen
{
public override ScreenType Type => ScreenType.Component;
public Header? Header { get; init; }
public Footer? Footer { get; init; }
public bool? ShowStuff {get; init; }
}
public abstract class Header : ITyped<HeaderType>
{
public abstract HeaderType Type { get; }
}
public enum HeaderType
{
Top,
b,
c,
d
}
public sealed class TopScreenHeader : Header
{
public override HeaderType Type => HeaderType.Top;
public string MyStuff { get; }
}
It isn't possible to just change all the abstract types, or writing converters, since there are multiple abstract types with times X derived objects. The JSON is also not consistent.
var screen = JsonConvert.DeserializeObject<Screen>(jsonString, new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.Objects,
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
Which doesn't work and gives errors:
Could not create an instance of type Screens.Screen. Type is an interace or abstract class and cannot be instantiated. Path 'screen', line 1, position 10.
Deserializing with TypeNameHandling is only possible if the $type property is set that maps to the exact object it should deserialize to. Without a $type property it does not deserialize with typenamehandling to my nested abstract objects.
The $type is set when serializing the json with newtonsoft and TypeNameHandling as setting. So the received JSON should be deserialized correctly.
If this isn't possible, than there are 2 "solutions":
For me writing code inplace of converters was easier and faster. I am using it for a prototype, but I wouldn't recommend it for production.
Both are errorprone to changes made in the package. And therefore aren't a solid solution, but it is the solution for the specific constraints and goal.
{
"$type": "MyPackage.Screens.Screen, MyPackage"
"screen":{
"$type": "MyPackage.Screens.ComponentScreen, MyPackage",
"type":"Component",
"footer":{
"$type": "MyPackage.Footer.BarFooter, MyPackage",
"type":"Bar"
},
"header":{
"$type": "MyPackage.Header.TopHeader, MyPackage",
"type":"Top"
}
}
}
In my case the "type" property is not necessary for deserializing and can be removed.
To deserialize in code you need to use a wrapper where the screen is defined. Because deserializing directly to screen (abstract) is not possible.
The $type should be above the type property otherwise it isn't handled correctly. I don't know why this is the case.