Дессериализация JSON для абстрактного класса

Я пытаюсь десериализовать строку JSON в конкретный класс, который наследуется от абстрактного класса, но я просто не могу заставить его работать. Я googled и попробовал некоторые решения, но они, похоже, не работают.

Это то, что у меня есть сейчас:

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);
}

Однако, если я попытаюсь применить результирующий объект, он просто не работает.

Причина, по которой я не использую DeserializeObject, заключается в том, что у меня есть несколько конкретных классов.

Любые предложения?

  • Я использую Newtonsoft.Json

Ответ 1

Нельзя использовать TypeNameHandling (потому что требуется более компактный json или хочет использовать определенное имя для переменной типа, отличной от "$ type" ). Между тем, подход customerCreationConverter не будет работать, если вы захотите десериализовать базовый класс на любой из нескольких производных классов, не зная, какой из них следует использовать заранее.

Альтернативой является использование int или другого типа в базовом классе и определение 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; }
}

JsonConverter для базового класса может затем десериализовать объект на основе его типа. Усложнение состоит в том, что во избежание (когда JsonConverter неоднократно вызывает себя), во время этой десериализации должен использоваться пользовательский разрешитель контрактов.

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
    }
}

Что это. Теперь вы можете использовать serialize/deserialize любой производный класс. Вы также можете использовать базовый класс в других классах и сериализовать/десериализовать их без дополнительной работы:

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);

Ответ 2

попробуйте что-то вроде этого

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

UPDATE
для этого сценария все работает так, как вы хотите

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);

где c тип test.Base {test.Der}

UPDATE

@Gusman используйте TypeNameHandling.Objects вместо TypeNameHandling.All. Этого достаточно, и он будет производить менее подробную сериализацию.

Ответ 3

Я бы предложил использовать CustomCreationConverter следующим образом:

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();
            }
        }
    }

Ответ 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; } }
}

Теперь добавьте этот конвертер в JsonSerializerSettings, как показано ниже

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

После добавления сериализации или десериализации объекта базового класса ниже

 JsonConvert.DeserializeObject<Type>("json string", jsonSerializerSettings );

Ответ 5

На самом деле, как было указано в обновлении, самый простой способ (в 2019 году) - использовать простые предопределенные JsonSerializerSettings, как описано здесь.

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

И для десериализации:

TDSPriceModels models = JsonConvert.DeserializeObject<TDSPriceModels>(File.ReadAllText(jsonPath), new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All
        });

Ответ 6

Для всех тех, кто все еще сталкивается с этой проблемой, здесь есть немного более общий json конвертер, который я написал. Это десериализует любой абстрактный класс, который имеет атрибуты KnownType для каждого конкретного типа. https://gist.github.com/ahmeturun/a0bdd4cc079d1fc3086ce789a272bbe4