64

I am trying to deserialize a JSON string to a concrete class, which inherits from an abstract class, but I just can't get it working. I have googled and tried some solutions but they don't seem to work either.

This is what I have now:

abstract class AbstractClass { }

class ConcreteClass { }

public AbstractClass Decode(string jsonString)
{
    JsonSerializerSettings jss = new JsonSerializerSettings();
    jss.TypeNameHandling = TypeNameHandling.All;
    return (AbstractClass)JsonConvert.DeserializeObject(jsonString, null, jss);
}

However, if I try to cast the resulting object, it just doesn't work.

The reason why I don't use DeserializeObject is that I have many concrete classes.

Any suggestions?

  • I am using Newtonsoft.Json
aochagavia
  • 5,887
  • 5
  • 34
  • 53
  • for deserialization you need create object instance, but you can't create instance of abstract class – Grundy Jan 08 '14 at 12:38
  • 2
    I want it to create an instance of a concrete class, sorry if it wasn't clear – aochagavia Jan 08 '14 at 12:39
  • can you provide a bit more code? – Grundy Jan 08 '14 at 12:40
  • 3
    possible duplicate of [Deserializing polymorphic json classes without type information using json.net](http://stackoverflow.com/questions/19307752/deserializing-polymorphic-json-classes-without-type-information-using-json-net) – Brian Rogers Feb 18 '15 at 14:22
  • Best answer deserializing without type information : http://stackoverflow.com/a/30579193/1214248 using newtonsoft.json – JB. Jul 15 '15 at 12:56

7 Answers7

94

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);
StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
mbabramo
  • 2,573
  • 2
  • 20
  • 24
  • 1
    This is a great solution; the only improvement I would suggest is that instead of calling `return JsonConvert.DeserializeObject(jo.ToString(), SpecifiedSubclassConversion);`, you use the passed in serializer `Return jo.ToObject(t, serializer);` – James Joyce Jul 10 '16 at 13:14
  • 3
    then you'll get the stackoverflow exception – Andrey Ilnitsky Aug 25 '16 at 23:02
  • This is absolutely wonderful! Thank you very much for the code! – Mikhail Glukhov Jul 16 '17 at 14:50
  • This is very helpfull. Can this also be used for nested json? Like in this example if the objects with ObjType 2 also have arrays with different objects. – Zeebats Apr 06 '18 at 14:07
  • This should be the accepted answer. What about using `SpecifiedConcreteClassConverter` for more generic usage? – Cid Nov 14 '18 at 08:55
  • If anyone else is wondering how to use this a different way: `JsonConverter[] converters = { new BaseConverter()}; var test = JsonConvert.DeserializeObject>(result, new JsonSerializerSettings() { Converters = converters });` see: https://blog.codeinside.eu/2015/03/30/json-dotnet-deserialize-to-abstract-class-or-interface/ – maxshuty May 31 '19 at 18:53
  • 1
    This does not work very well if a custom JSON serializer is used, and using the passed serializer instead will cause a recursive call that will fail. Anyone considering this solution may want to have a look at https://stackoverflow.com/a/48947235/2279059 as well, which is similar, but doesn't have this issue, and is more simple, too. – Florian Winter Jun 29 '20 at 13:41
  • 1
    This is great, however doing this will easily mess up the intellisense for future programmers, as it adds all this additional classes to the system. I wish there was a way to somehow hide them... – Cool guy Jan 05 '23 at 11:38
25

I would suggest to use CustomCreationConverter in the following way:

public enum ClassDiscriminatorEnum
    {
        ChildClass1,
        ChildClass2
    }

    public abstract class BaseClass
    {
        public abstract ClassDiscriminatorEnum Type { get; }
    }

    public class Child1 : BaseClass
    {
        public override ClassDiscriminatorEnum Type => ClassDiscriminatorEnum.ChildClass1;
        public int ExtraProperty1 { get; set; }
    }

    public class Child2 : BaseClass
    {
        public override ClassDiscriminatorEnum Type => ClassDiscriminatorEnum.ChildClass2;
    }

    public class BaseClassConverter : CustomCreationConverter<BaseClass>
    {
        private ClassDiscriminatorEnum _currentObjectType;

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jobj = JObject.ReadFrom(reader);
            _currentObjectType = jobj["Type"].ToObject<ClassDiscriminatorEnum>();
            return base.ReadJson(jobj.CreateReader(), objectType, existingValue, serializer);
        }

        public override BaseClass Create(Type objectType)
        {
            switch (_currentObjectType)
            {
                case ClassDiscriminatorEnum.ChildClass1:
                    return new Child1();
                case ClassDiscriminatorEnum.ChildClass2:
                    return new Child2();
                default:
                    throw new NotImplementedException();
            }
        }
    }
Denis
  • 514
  • 5
  • 8
  • 3
    This is by far the best answer to the question, thank you very much! – Snicker Aug 22 '18 at 10:46
  • This even works if a custom JSON serializer is used. The custom serializer is used to deserialize the derived object. – Florian Winter Jun 29 '20 at 13:39
  • it didnt work for me when using baseclass as a property in the api controller - it wont deserialize? am i missing something? – lemon Feb 24 '21 at 01:39
  • 1
    I also think this is the cleanest solution, but it's not thread safe. _currentObjectType can potentially be overwritten from other threads. I've made it thread safe by determining the concrete type in ReadJson and passing that type to base.ReadJson. The Create method then receives these concrete types and can create instances of them with: (BaseClass)Activator.CreateInstance(objectType). – Eric Dec 04 '22 at 13:58
23

try something like this

public AbstractClass Decode(string jsonString)
{
    var jss = new JavaScriptSerializer();
    return jss.Deserialize<ConcreteClass>(jsonString);
}

UPDATE
for this scenario methinks all work as you want

public abstract class Base
{
    public abstract int GetInt();
}
public class Der:Base
{
    int g = 5;
    public override int GetInt()
    {
        return g+2;
    }
}
public class Der2 : Base
{
    int i = 10;
    public override int GetInt()
    {
        return i+17;
    }
}

....

var jset = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All };
Base b = new Der()
string json = JsonConvert.SerializeObject(b, jset);
....

Base c = (Base)JsonConvert.DeserializeObject(json, jset);

where c type is test.Base {test.Der}

UPDATE

@Gusman suggest use TypeNameHandling.Objects instead of TypeNameHandling.All. It is enough and it will produce a less verbose serialization.

Grundy
  • 13,356
  • 3
  • 35
  • 55
23

Actually, as it has been stated in an update, the simplest way (in 2019) is to use a simple custom pre-defined JsonSerializerSettings, as explained here

        string jsonTypeNameAll = JsonConvert.SerializeObject(priceModels, Formatting.Indented,new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All
        });

And for deserializing :

TDSPriceModels models = JsonConvert.DeserializeObject<TDSPriceModels>(File.ReadAllText(jsonPath), new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All
        });
XavierAM
  • 1,627
  • 1
  • 14
  • 30
  • 3
    Note that if you could also use `TypeNameHandling.Auto` instead. This setting will let JsonConvert decide when it needs to handle the `$type` when it really needs it. That can save a lot of space from the final file. – Pic Mickael Jan 07 '20 at 19:05
  • Please mind [CA2326: Do not use TypeNameHandling values other than None](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2326) – Hans Kesting May 24 '22 at 13:06
4
 public class CustomConverter : JsonConverter
{
    private static readonly JsonSerializer Serializer = new JsonSerializer();

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);
        var typeString = jObject.Value<string>("Kind"); //Kind is a property in json , from which we know type of child classes
        var requiredType = RecoverType(typeString);

        return Serializer.Deserialize(jObject.CreateReader(), requiredType);
    }

    private Type RecoverType(string typeString)
    {
        if (typeString.Equals(type of child class1, StringComparison.OrdinalIgnoreCase))
            return typeof(childclass1);
        if (typeString.Equals(type of child class2, StringComparison.OrdinalIgnoreCase))
            return typeof(childclass2);            

        throw new ArgumentException("Unrecognized type");
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(Base class).IsAssignableFrom(objectType) || typeof((Base class) == objectType;
    }

    public override bool CanWrite { get { return false; } }
}

Now add this converter in JsonSerializerSettings as below

   var jsonSerializerSettings = new JsonSerializerSettings();
        jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
        jsonSerializerSettings.Converters.Add(new CustomConverter());

After adding serialize or deserialize base class object as below

 JsonConvert.DeserializeObject<Type>("json string", jsonSerializerSettings );
Dalip Choudhary
  • 546
  • 5
  • 18
  • Why the static `JsonSerializer` instance rather than using the one that was passed into ReadJson – eddiewould Jul 19 '18 at 02:52
  • I've combined this with the suggestion from https://stackoverflow.com/questions/46036138/how-to-ignore-base-class-jsonconverter-when-deserializing-derived-classes - works a trick. No need for the static JsonSerializer. – eddiewould Jul 19 '18 at 03:23
  • The `jsonSerializerSettings` should also be added in the `ReadJson` function, otherwise recursions won't work. – Dodo Jan 07 '22 at 09:08
2

I had a similar issue, and I solved it with another way, maybe this would help someone: I have json that contains in it several fields that are always the same, except for one field called "data" that can be a different type of class every time. I would like to de-serialize it without analayzing every filed specific. My solution is: To define the main class (with 'Data' field) with , the field Data is type T. Whenever that I de-serialize, I specify the type:

MainClass:

public class MainClass<T>
{
    [JsonProperty("status")]
    public Statuses Status { get; set; }

    [JsonProperty("description")]
    public string Description { get; set; }

    [JsonProperty("data")]
    public T Data { get; set; }

    public static MainClass<T> Parse(string mainClsTxt)
    {
        var response = JsonConvert.DeserializeObject<MainClass<T>>(mainClsTxt);
        return response;

    }
} 

User

public class User
{

    [JsonProperty("id")]
    public int UserId { get; set; }

    [JsonProperty("first_name")]
    public string FirstName { get; set; }

    [JsonProperty("last_name")]
    public string LastName { get; set; }

}

Product

public class Product
{

    [JsonProperty("product_id")]
    public int ProductId { get; set; }

    [JsonProperty("product_name")]
    public string ProductName { get; set; }

    [JsonProperty("stock")]
    public int Stock { get; set; }

}

Using

var v = MainClass<User>.Parse(userJson);
var v2 = MainClass<Product>.Parse(productJson);

json example

userJson: "{"status":1,"description":"my description","data":{"id":12161347,"first_name":"my fname","last_name":"my lname"}}"

productJson: "{"status":1,"description":"my description","data":{"product_id":5,"product_name":"my product","stock":1000}}"
1
public abstract class JsonCreationConverter<T> : JsonConverter
{
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType;
    }

    public override object ReadJson(JsonReader reader,Type objectType,
        object existingValue, JsonSerializer serializer)
    {
        try
        {
            var jObject = JObject.Load(reader);
            var target = Create(objectType, jObject);
            serializer.Populate(jObject.CreateReader(), target);
            return target;
        }
        catch (JsonReaderException)
        {
            return null;
        }
    }

    public override void WriteJson(JsonWriter writer, object value,
        JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Now implement this interface

public class SportActivityConverter : JsonCreationConverter<BaseSportActivity>
{
    protected override BaseSportActivity Create(Type objectType, JObject jObject)
    {
        BaseSportActivity result = null;
        try
        {
            switch ((ESportActivityType)jObject["activityType"].Value<int>())
            {
                case ESportActivityType.Football:
                    result = jObject.ToObject<FootballActivity>();
                    break;

                case ESportActivityType.Basketball:
                    result = jObject.ToObject<BasketballActivity>();
                    break;
            }
            
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex);
        }

        return result;
        
    }
}
Amir Touitou
  • 3,141
  • 1
  • 35
  • 31
  • 1
    Only works if you decorate your baseclass with: `[JsonConverter(typeof(BaseConverter))]` and add the `CanWrite` function with `return false` to the class `SportActivityConverter`. – Dodo Jan 07 '22 at 09:05