web-dev-qa-db-ja.com

JSONを抽象クラスにデシリアライズする

JSON文字列を具象クラスにデシリアライズしようとしていますが、このクラスは抽象クラスから継承していますが、動作させることができません。私はグーグルで検索していくつかの解決策を試しましたが、うまくいかないようです。

これは私が今持っているものです:

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を使用しています
32
aochagavia

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

それでおしまい。これで、任意の派生クラスをシリアライズ/デシリアライズできます。他のクラスで基本クラスを使用し、追加の作業なしでそれらをシリアル化/逆シリアル化することもできます。

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

このようなものを試してください

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 typeはtest.Base {test.Der}

[〜#〜] update [〜#〜]

@ Gusman 使用の提案TypeNameHandling.Objects の代わりに TypeNameHandling.All。これで十分であり、より冗長なシリアル化が生成されます。

21
Grundy

次の方法で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();
            }
        }
    }
8
Denis
 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 );
3
Dalip Choudhary

実際、アップデートで述べられているように、最も簡単な方法(2019年)は、説明されているように、シンプルなカスタム定義済みJsonSerializerSettingsを使用することです here

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