Deserializing JSON to Abstract Class

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



Leave a reply



Submit