2014-01-08 5 views
11

Я пытаюсь десериализовать строку JSON в конкретный класс, который наследуется от абстрактного класса, но я просто не могу заставить его работать. Я googled и попробовал некоторые решения, но они, похоже, не работают.Deserializing 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
+0

для десериализации вам нужно создать экземпляр объекта, но вы не можете создать экземпляр абстрактного класса – Grundy

+0

Я хочу, чтобы создать экземпляр конкретного класса , извините, если было не ясно – aochagavia

+0

вы можете предоставить немного больше кода? – Grundy

ответ

8

попробовать что-то вроде этого

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

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

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. Этого достаточно, и он будет производить менее подробные сериализации.

+1

Проблема в том, что у меня много конкретных классов, поэтому я не знаете, какой из них использовать – aochagavia

+0

@aochagavia, поэтому сделайте свой абстрактный класс не абстрактным и используйте его :-) – Grundy

+2

Он имеет абстрактные методы, поэтому он абстрактный ... Я просто добавлю больше информации о JSON, например classType или что-то типа того. – aochagavia

26

Возможно, вы не захотите использовать TypeNameHandling (потому что требуется более компактный json или хочет использовать определенное имя для переменной типа, отличной от «$ type»). Между тем, customerCreationConverter 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 
    } 
} 

Всё. Теперь вы можете использовать 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); 
+0

Это отличное решение; единственное улучшение, которое я бы предложил, заключается в том, что вместо вызова 'return JsonConvert.DeserializeObject (jo.ToString(), SpecifiedSubclassConversion);', вы используете переданный в сериализаторе 'Return jo.ToObject (t, serializer);' –

+0

то вы получите исключение stackoverflow –

+0

Это абсолютно замечательно! Большое спасибо за код! –

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

Я хотел бы предложить использовать 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(); 
      } 
     } 
    } 
Смежные вопросы