Deserializing JSON to abstract class
One may not want to use TypeNameHandling (because one wants more compact json or wants to use a specific name for the type variable other than "$type"). Meanwhile, the customCreationConverter approach will not work if one wants to deserialize the base class into any of multiple derived classes without knowing which one to use in advance.
An alternative is to use an int or other type in the base class and define a JsonConverter.
[JsonConverter(typeof(BaseConverter))]
abstract class Base
{
public int ObjType { get; set; }
public int Id { get; set; }
}
class DerivedType1 : Base
{
public string Foo { get; set; }
}
class DerivedType2 : Base
{
public string Bar { get; set; }
}
The JsonConverter for the base class can then deserialize the object based on its type. The complication is that to avoid a stack overflow (where the JsonConverter repeatedly calls itself), a custom contract resolver must be used during this deserialization.
public class BaseSpecifiedConcreteClassConverter : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (typeof(Base).IsAssignableFrom(objectType) && !objectType.IsAbstract)
return null; // pretend TableSortRuleConvert is not specified (thus avoiding a stack overflow)
return base.ResolveContractConverter(objectType);
}
}
public class BaseConverter : JsonConverter
{
static JsonSerializerSettings SpecifiedSubclassConversion = new JsonSerializerSettings() { ContractResolver = new BaseSpecifiedConcreteClassConverter() };
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Base));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
switch (jo["ObjType"].Value<int>())
{
case 1:
return JsonConvert.DeserializeObject<DerivedType1>(jo.ToString(), SpecifiedSubclassConversion);
case 2:
return JsonConvert.DeserializeObject<DerivedType2>(jo.ToString(), SpecifiedSubclassConversion);
default:
throw new Exception();
}
throw new NotImplementedException();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // won't be called because CanWrite returns false
}
}
That's it. Now you can use serialize/deserialize any derived class. You can also use the base class in other classes and serialize/deserialize those without any additional work:
class Holder
{
public List<Base> Objects { get; set; }
}
string json = @"
[
{
""Objects"" :
[
{ ""ObjType"": 1, ""Id"" : 1, ""Foo"" : ""One"" },
{ ""ObjType"": 1, ""Id"" : 2, ""Foo"" : ""Two"" },
]
},
{
""Objects"" :
[
{ ""ObjType"": 2, ""Id"" : 3, ""Bar"" : ""Three"" },
{ ""ObjType"": 2, ""Id"" : 4, ""Bar"" : ""Four"" },
]
},
]";
List<Holder> list = JsonConvert.DeserializeObject<List<Holder>>(json);
string serializedAgain = JsonConvert.SerializeObject(list);
Debug.WriteLine(serializedAgain);
JsonConvert deserialize an array of abstract classes
As per @Evk's shared link, you should try setting TypeNameHandling
to TypeNameHandling.Auto
while serializing and deserializing:
var toJson = JsonConvert.SerializeObject(street, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
var fromJson = JsonConvert.DeserializeObject<Street>(toJson, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
How to deserialize downloaded JSON string to abstract class C# with Newtonsoft.Json
You can't create an object of abstract class:
MSDN: Abstract classes are closely related to interfaces. They are classes that cannot be instantiated, and are frequently either partially implemented, or not at all implemented. One key difference between abstract classes and interfaces is that a class may implement an unlimited number of interfaces, but may inherit from only one abstract (or any other kind of) class. A class that is derived from an abstract class may still implement interfaces. Abstract classes are useful when creating components because they allow you specify an invariant level of functionality in some methods, but leave the implementation of other methods until a specific implementation of that class is needed. They also version well, because if additional functionality is needed in derived classes, it can be added to the base class without breaking code.
Just inherit some class from your abstract class (with no body), and deserialize to it.
For example:
public class YourClassName : JsonClasses
{
}
UPDATE
For example I've created an example generic method, that could help:
public T DeserializeFromJsonClasses<T>() where T : JsonClasses
{
T myObj = default(T);
JsonSerializerSettings settings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All};
myObj = JsonConvert.DeserializeObject<T>(this.Response, settings);
return myObj;
}
Deserialize an abstract class that has only a getter using Newtonsoft
You should do that like this dude:
A marker interface for detecting the type or deserializing
A container class
Dto classes
//marker interface
public interface Info { }
public class HealthInfo : Info
{
public int MoreHealth { set; get; }
public int AttackDamage { set; get; }
}
public class SpellInfo : Info
{
public int Effect { set; get; }
public int EffectAmount { set; get; }
}
public class Card<T> where T : Info
{
public Card(string name, int health, T info)
{
this.Info = info;
this.Name = name;
this.Health = health;
}
public T Info { private set; get; }
public string Name { set; get; }
public int Health { set; get; }
}
public class InfoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType == typeof(Card<Info>))
{
return true;
}
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
if (jObject.ContainsKey("TypeName"))
{
string typeName = jObject["TypeName"]?.ToString()?.Trim()?.ToLower();
if (typeName?.Equals("monsterinfo") == true)
{
Card<HealthInfo> deseerialized = jObject.ToObject<Card<HealthInfo>>();
return new Card<Info>(deseerialized.Name, deseerialized.Health, deseerialized.Info);
}
if (typeName?.Equals("spellinfo") == true)
{
string json = jObject.ToString();
Card<SpellInfo> deseerialized = jObject.ToObject<Card<SpellInfo>>();
return new Card<Info>(deseerialized.Name, deseerialized.Health, deseerialized.Info);
}
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And you should execute:
List<Card<Info>> list = JsonConvert.DeserializeObject<List<Card<Info>>>(jsonText, new InfoConverter());
Deserializing JSON to a Dictionary string, Item with Item being abstract
You can use custom converter to be able to deserialize to different types in same hierarchy. Also I highly recommend using properties instead of fields. So small reproducer can look like this:
public abstract class Item
{
public virtual ItemType Type => ItemType.NONE; // expression-bodied property
public enum ItemType
{
NONE,
WEAPON,
ARMOUR,
CONSUMABLE,
}
}
public class Weapon : Item
{
public override ItemType Type => ItemType.WEAPON; // expression-bodied property
public string SomeWeaponProperty { get; set; }
}
Custom converter:
public class ItemConverter : JsonConverter<Item>
{
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, Item? value, JsonSerializer serializer) => throw new NotImplementedException();
public override Item ReadJson(JsonReader reader, Type objectType, Item existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// TODO handle nulls
var jObject = JObject.Load(reader);
Item result;
switch (jObject.GetValue("type", StringComparison.InvariantCultureIgnoreCase).ToObject<Item.ItemType>(serializer))
{
case Item.ItemType.WEAPON:
result = jObject.ToObject<Weapon>();
break;
// handle other types
// case Item.ItemType.ARMOUR:
// case Item.ItemType.CONSUMABLE:
case Item.ItemType.NONE:
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
}
and example usage (or mark Item
with JsonConverterAttribute
):
var item = new Weapon();
var settings = new JsonSerializerSettings
{
Converters = { new ItemConverter() }
};
string json = JsonConvert.SerializeObject(item, settings);
var res = JsonConvert.DeserializeObject<Item>(json, settings);
Console.WriteLine(res.GetType()); // prints Weapon
Deserializing JSON into an object having abstract and non-abstract classes
For those encountering the same issues, I have solved my problem through a combination of different answers out there.
Classes Item and Node have been attributed with the JSON custom converter:
[JsonConverter(typeof(JSONTreeConverter))]
public abstract class Node
[JsonConverter(typeof(JSONTreeConverter))]
public class Item
this enables each class to use the custom converter before initializing.
Then the custom converter returns an object which is cast through reflection to the proper Node or Item type.
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object targetObj = null;
JObject jo = JObject.Load(reader);
try
{
targetObj = Activator.CreateInstance(objectType); //instantiating concrete and known types
}
catch (Exception exc)
{
switch ((Node.NodeType)jo["Type"].Value<int>())
{
case Node.NodeType.Root:
targetObj = new Root();
break;
case Node.NodeType.Group:
targetObj = new Group();
break;
case Node.NodeType.Category:
targetObj = new ValescoCategory();
break;
case Node.NodeType.Type:
targetObj = new Type();
break;
case Node.NodeType.SubType:
targetObj = new SubType();
break;
case Node.NodeType.SubsubType:
targetObj = new SubSubType();
break;
case Node.NodeType.Item:
targetObj = new Item(); //now this is possible ;)
break;
}
}
foreach (PropertyInfo prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
Deserializing as such: JsonConvert.DeserializeObject<Tree>(tree);
That's it! The other overridden function of JSON all return false with Write being not implemented.
Hope this helps someone else because it has taken me 3 days to get to.
How to deserialize json to an abstract class in spring-boot
You can create a custom deserializer which will create Question
instances based on json payload properties.
For example if the Question
class looks like this:
public class Question {
private final String name;
@JsonCreator
Question(@JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
And sub-class TrueAndFalse
:
public class TrueAndFalse extends Question {
private final String description;
@JsonCreator
TrueAndFalse(@JsonProperty("name") String name,
@JsonProperty("description") String description) {
super(name);
this.description = description;
}
public String getDescription() {
return description;
}
}
Then you can create a deserializer, which will create an instance of TrueAndFale
sub-class by checking if it has description
property:
public class QuestionDeserializer extends JsonDeserializer<Question> {
@Override
public Question deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
ObjectCodec codec = p.getCodec();
JsonNode tree = codec.readTree(p);
if (tree.has("description")) {
return codec.treeToValue(tree, TrueAndFalse.class);
}
// Other types...
throw new UnsupportedOperationException("Cannot deserialize to a known type");
}
}
And afterwards, make sure to register it on the object mapper:
@Configuration
public class ObjectMapperConfiguration {
@Bean
public ObjectMapper objectMapper() {
SimpleModule module = new SimpleModule();
module.addDeserializer(Question.class, new QuestionDeserializer());
return new ObjectMapper().registerModules(module);
}
}
Related Topics
Getting the Absolute Path of the Executable, Using C#
How to Get the Index of an Element in an Ienumerable
How to Assign a Func<> Conditionally Between Lambdas Using the Conditional Ternary Operator
Not Ableto Serialize Dictionary with Complex Key Using JSON.Net
How to Connect to an Mdf Database File
How Big Is an Object Reference in .Net
Why Does "Int[] Is Uint[] == True" in C#
Why Is the 'This' Keyword Required to Call an Extension Method from Within the Extended Class
Fastest Method to Remove Empty Rows and Columns from Excel Files Using Interop
Serialize Dictionary as Array (Of Key Value Pairs)
C# - Get a List of Files Excluding Those That Are Hidden
This.Visible Is Not Working in Windows Forms
Deserializing into a List Without a Container Element in Xml
Abstract Classes VS Interfaces