【问题标题】:Deserializing JSON to abstract class将 JSON 反序列化为抽象类
【发布时间】:2014-01-08 12:37:12
【问题描述】:

我正在尝试将 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

【问题讨论】:

标签: c# json serialization json.net


【解决方案1】:

人们可能不想使用 TypeNameHandling(因为人们想要更紧凑的 json 或者想要为类型变量使用特定名称而不是“$type”)。同时,如果想将基类反序列化为多个派生类中的任何一个,而事先不知道要使用哪个派生类,customCreationConverter approach 将不起作用。

另一种方法是在基类中使用 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);

【讨论】:

  • 这是一个很好的解决方案;我建议的唯一改进是不要调用return JsonConvert.DeserializeObject&lt;DerivedType2&gt;(jo.ToString(), SpecifiedSubclassConversion);,而是使用传入的序列化程序Return jo.ToObject(t, serializer);
  • 然后你会得到stackoverflow异常
  • 这真是太棒了!非常感谢您的代码!
  • 这很有帮助。这也可以用于嵌套的json吗?就像在这个例子中,如果 ObjType 2 的对象也有不同对象的数组。
  • 如果使用自定义 JSON 序列化器,这将无法很好地工作,而使用传递的序列化器将导致递归调用失败。任何考虑这个解决方案的人可能也想看看stackoverflow.com/a/48947235/2279059,它是类似的,但没有这个问题,也更简单。
【解决方案2】:

我建议通过以下方式使用 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();
            }
        }
    }

【讨论】:

  • 这是迄今为止问题的最佳答案,非常感谢!
  • 如果使用自定义 JSON 序列化程序,这甚至可以工作。自定义序列化器用于反序列化派生对象。
  • 在 api 控制器中使用基类作为属性时它对我不起作用 - 它不会反序列化?我错过了什么吗?
【解决方案3】:

试试这样的

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

更新
对于这种情况,我认为所有工作都随心所欲

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}

更新

@Gusman 建议使用TypeNameHandling.Objects 而不是TypeNameHandling.All。已经足够了,它会产生一个不那么冗长的序列化。

【讨论】:

  • 问题是我有很多具体的类,所以不知道用哪一个
  • @aochagavia 所以让你的抽象类不抽象并使用它:-)
  • 它有抽象方法,这就是为什么它是抽象的......我只会在 JSON 上提供更多信息,比如 classType 或类似的东西。
  • 您能提供示例您现在如何尝试使用它吗?
  • @aochagavia 以及你如何序列化你的对象
【解决方案4】:

实际上,正如更新中所述,最简单的方法(在 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
        });

【讨论】:

  • 请注意,如果您也可以改用TypeNameHandling.Auto。此设置将让 JsonConvert 在真正需要时决定何时需要处理 $type。这可以从最终文件中节省大量空间。
【解决方案5】:
 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 );

【讨论】:

  • 为什么使用静态 JsonSerializer 实例而不是使用传入 ReadJson 的实例
  • 我已经将这个与stackoverflow.com/questions/46036138/… 的建议结合起来——奏效了。不需要静态 JsonSerializer。
  • jsonSerializerSettings 也应该添加到ReadJson 函数中,否则递归将不起作用。
【解决方案6】:

我有一个类似的问题,我用另一种方式解决了它,也许这会对某人有所帮助: 我有一个 json,其中包含几个始终相同的字段,除了一个名为“data”的字段,它每次都可以是不同类型的类。 我想反序列化它而不分析每个特定的文件。 我的解决方案是: 要使用 定义主类(带有“Data”字段),字段 Data 是类型 T。 每当我反序列化时,我都会指定类型:

主类:

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;

    }
} 

用户

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

}

产品

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

}

使用

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

json 示例

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

【讨论】:

  • 我在找什么,谢谢!
【解决方案7】:
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();
    }
}

现在实现这个接口

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

【讨论】:

  • 仅当您使用[JsonConverter(typeof(BaseConverter))] 装饰您的基类并将带有return falseCanWrite 函数添加到类SportActivityConverter 时才有效。
猜你喜欢
  • 2012-11-18
  • 1970-01-01
  • 2010-10-12
  • 2016-12-18
  • 2011-04-27
  • 1970-01-01
  • 2016-07-07
  • 2017-11-06
  • 1970-01-01
相关资源
最近更新 更多