2016-04-12 23 views
1

Satır nesneleri listesini içeren bir Kabin sınıfım var. Bu gibi nesneleri serileştirmek isterdim, ancak seri hale getirirken, Row nesnelerini Row nesnesinden gelen RowRule nesneleri olmasını isterdim. Aşağıda denediğim örnek kod var.Json nesnelerini belirli alt sınıflara nasıl ayırırsınız?

class Program 
{ 
    static void Main(string[] args) 
    { 
     var cabin = new Cabin(); 
     var row = new Row(); 
     row.Status = "Success"; 
     cabin.Rows = new List<Row>() 
     { 
      row, 
      row 
     }; 

     JsonSerializerSettings settings = new JsonSerializerSettings() 
     { 
      TypeNameHandling = TypeNameHandling.Auto 
     }; 
     string json = JsonConvert.SerializeObject(cabin, Formatting.Indented, settings); 
     Console.WriteLine(json); 

     Cabin obj = JsonConvert.DeserializeObject<Cabin>(json, 
      new JsonSerializerSettings() {TypeNameHandling = TypeNameHandling.Auto}); 
     Console.WriteLine(obj); 
     Debug.Assert(obj.Rows.First().GetType().Name == "RowRule"); 
    } 
} 

class Cabin 
{ 
    public IList<Row> Rows { get; set; } 
} 

class Row 
{ 
    public string Status { get; set; } 
} 

class RowRule : Row 
{ 

} 
+0

Neden "TypeNameHandling.Auto' kullanıyorsunuz? "Satır" ın diğer alt sınıflarının listede bulunma olasılığı var mı, ve tür başka türlü belirtilmediğinde sadece "Satır" dan "RowRule" e dönüşmek mi istiyorsunuz? – dbc

+0

İlgili: [Json.net'te özel JsonConverter ve TypeNameHandling'i kullanma] (https://stackoverflow.com/q/29810004/3744182). – dbc

cevap

2

basit bir cevabı bir CustomCreationConverter<Row> kullanıp döndürmektir bir RowRuleCreate() dan:

böyle yapmak istedim olabilir Ancak

class RowToRoleRuleConverter : CustomCreationConverter<Row> 
{ 
    public override Row Create(Type objectType) 
    { 
     if (objectType.IsAssignableFrom(typeof(RowRule))) 
      return Activator.CreateInstance<RowRule>(); 
     return (Row)Activator.CreateInstance(objectType); 
    } 
} 

, kullandığınız TypeNameHandling.Auto hangi JSON'unuzda polimorfik "$type" özelliklerinin olabileceğini ima eder. Ne yazık ki, CustomCreationConverter<T> bu özellikleri yok sayar. Böylece bazı ek iş yapmak ve DowncastingConverter<TBase, TDerived> oluşturmak için gerekli olacaktır:

public class DowncastingConverter<TBase, TDerived> : PolymorphicCreationConverter<TBase> where TDerived : TBase 
{ 
    protected override TBase Create(Type objectType, Type polymorphicType, object existingValue, IContractResolver contractResolver, JObject obj) 
    { 
     Type createType = objectType; 
     if (createType.IsAssignableFrom(polymorphicType)) 
      createType = polymorphicType; 
     if (createType.IsAssignableFrom(typeof(TDerived))) 
      createType = typeof(TDerived); 

     if (existingValue != null && createType.IsAssignableFrom(existingValue.GetType())) 
      return (TBase)existingValue; 

     var contract = contractResolver.ResolveContract(createType); 
     return (TBase)contract.DefaultCreator(); 
    } 
} 

public abstract class PolymorphicCreationConverter<T> : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return typeof(T).IsAssignableFrom(objectType); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotSupportedException("CustomCreationConverter should only be used while deserializing."); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) 
      return null; 
     var obj = JObject.Load(reader); 
     Type polymorphicType = null; 

     var polymorphicTypeString = (string)obj["$type"]; 
     if (polymorphicTypeString != null) 
     { 
      if (serializer.TypeNameHandling != TypeNameHandling.None) 
      { 
       string typeName, assemblyName; 
       ReflectionUtils.SplitFullyQualifiedTypeName(polymorphicTypeString, out typeName, out assemblyName); 
       polymorphicType = serializer.Binder.BindToType(assemblyName, typeName); 
      } 
      obj.Remove("$type"); 
     } 

     var value = Create(objectType, polymorphicType, existingValue, serializer.ContractResolver, obj); 
     if (value == null) 
      throw new JsonSerializationException("No object created."); 

     using (var subReader = obj.CreateReader()) 
      serializer.Populate(subReader, value); 
     return value; 
    } 

    protected abstract T Create(Type objectType, Type polymorphicType, object existingValue, IContractResolver iContractResolver, JObject obj); 

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

internal static class ReflectionUtils 
{ 
    // Utilities taken from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs 
    // I couldn't find a way to access these directly. 

    public static void SplitFullyQualifiedTypeName(string fullyQualifiedTypeName, out string typeName, out string assemblyName) 
    { 
     int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName); 

     if (assemblyDelimiterIndex != null) 
     { 
      typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.GetValueOrDefault()).Trim(); 
      assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1).Trim(); 
     } 
     else 
     { 
      typeName = fullyQualifiedTypeName; 
      assemblyName = null; 
     } 
    } 

    private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName) 
    { 
     int scope = 0; 
     for (int i = 0; i < fullyQualifiedTypeName.Length; i++) 
     { 
      char current = fullyQualifiedTypeName[i]; 
      switch (current) 
      { 
       case '[': 
        scope++; 
        break; 
       case ']': 
        scope--; 
        break; 
       case ',': 
        if (scope == 0) 
        { 
         return i; 
        } 
        break; 
      } 
     } 

     return null; 
    } 
} 

Sonra gibi kullanın:

JsonSerializerSettings readSettings = new JsonSerializerSettings() 
{ 
    TypeNameHandling = TypeNameHandling.Auto, 
    Converters = new[] { new DowncastingConverter<Row, RowRule>() }, 
}; 
Cabin obj = JsonConvert.DeserializeObject<Cabin>(json, readSettings); 

Prototip fiddle.

+0

Uhm, üzgünüm, bu harika. Json.Net'in bir parçası olmalı. – JHo

0

sizin örnek kodla sorun nesne oluşturma ve mümkün olmadığı RowRule gitmeye çalışıyoruz vardır.

 var cabin = new Cabin(); 
     var row = new RowRule(); // create derived object 
     row.Status = "Success"; 
     cabin.Rows = new List<Row>() 
     { 
      row, 
      row 
     }; 
+0

Her iki durum için oluşturulan Json aynı olduğu sürece mümkündür. Newtonsoft, json cinsinden tür bilgilerini varsayılan olarak dahil etmez. – Dan

+0

'TypeNameHandling = TypeNameHandling.Auto' ile tür adını içerir. bkz. http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_TypeNameHandling.htm – vendettamit

+0

Ancak json dizesini kaldırarak düzenleyebilirsiniz, değil mi? – Dan

İlgili konular