2015-01-10 2 views
2

Я работаю над проектом C# .dotNet, который вызывает стороннюю службу REST.Использование DataContractJsonSerializer, десериализация строки JSON в объект C#, который имеет список и интерфейс как свойства

Пример Структура Класс:

[Serializable] 
[DataContract(Name = "MainClass")] 
[KnownType(typeof(Class1))] 
[KnownType(typeof(Class2))] 
public class MainClass : IMainInterface 
{ 

    public MainClass() 
    { 
     Value2 = new List<IClass2>(); 
    } 

    [DataMember(Name = "class")] 
    public IClass1 Value1 { get; set; } 

    [DataMember(Name = "classes")] 
    public List<IClass2> Value2 { get; set; } 

} 

[Serializable] 
[Export(typeof(IClass1))] 
[ExportMetadata("IClass1", "Class1")] 
[DataContract(Name = "class1")] 
public class Class1 : IClass1 
{ 
    [ImportingConstructor] 
    public Class1() 
    { 

    } 

    [DataMember(Name = "prop1")] 
    public string Prop1 { get; set; } 

    [DataMember(Name = "prop2")] 
    public string Prop2 { get; set; } 

    [DataMember(Name = "prop3")] 
    public string Prop3 { get; set; } 

} 

[Serializable] 
[Export(typeof(IClass2))] 
[ExportMetadata("IClass2", "Class2")] 
[DataContract] 
public class Class2 : IClass2 
{ 
    [ImportingConstructor] 
    public Class2() 
    { } 


    [DataMember(Name = "propA")] 
    public string PropA { get; set; } 

    [DataMember(Name = "propB")] 
    public string PropB { get; set; } 

    [DataMember(Name = "propC")] 
    public string PropC { get; set; } 
} 

public interface IMainInterface 
{ 
    IClass1 Value1 { get; set; } 

    List<IClass2> Value2 { get; set; } 
} 

public interface IClass1 
{ 
    string Prop1 { get; set; } 

    string Prop2 { get; set; } 

    string Prop3 { get; set; } 
} 

public interface IClass2 
{ 
    string PropA { get; set; } 

    string PropB { get; set; } 

    string PropC { get; set; } 
} 

Json_String1: (с типом намеком)

{ 
"class": 
    {"__type":"class1:#WpfApplication1","prop1":"TestVal0","prop2":"TestVal2","prop3":"TestVal3"}, 
"classes": 
    [ 
     {"__type":"Class2:#WpfApplication1","propA":"A","propB":"B","propC":"C"}, 
     {"__type":"Class2:#WpfApplication1","propA":"X","propB":"Y","propC":"Z"}, 
     {"__type":"Class2:#WpfApplication1","propA":"1","propB":"2","propC":"3"} 
    ] 
} 

Json_String2: (без типа намек)

{ 
"class": 
    {"prop1":"TestVal0","prop2":"TestVal2","prop3":"TestVal3"}, 
"classes": 
    [ 
     {"propA":"A","propB":"B","propC":"C"}, 
     {"propA":"X","propB":"Y","propC":"Z"}, 
     {"propA":"1","propB":"2","propC":"3"} 
    ] 
} 

Так, данный клас если я создаю json (объекта MainClass), используя DataContractJsonSerializer, я получаю Json_String1, и если я непосредственно десериализую, он отлично работает.

В то время, как в то время как GET данные Ting, ответ Json_String2 (без намека типа). Следовательно, при десериализации я получаю следующую ошибку.

Invalid Cast Exception

InvalidCastException был необработанным. Невозможно передать объект типа 'System.Object', чтобы ввести 'WpfApplication1.IClass2'.

Теперь я должен вручную модифицировать json (string manipulation), добавив подсказку типа, чтобы десериализовать его успешно.

Вопрос 1) Как я могу избежать использования Json String Manipulation для десериализации?

Вопрос 2) как я могу создать json без подсказки типа?

редактировать: 1. Добавлен IMainInterface который реализуется MainClass. 2. dotNet Framework 4

+0

Как вы называете ParseResponse в этом случае? Вы не можете назвать это ParseResponse ()? – Icepickle

+0

@Icepickle 'ParseResponse (jsonResponse);', где jsonResponse - ответ от вызова GET Api. Структура Json определена, поэтому анализ класса 2 не даст желаемого результата. – Bhramar

+0

Тогда в вашем MainClass вы можете сделать «Список ' '' '' no? как базовый класс? я имею в виду, каким-то образом он должен создавать экземпляры переменных во время десериализации и интерфейс, который вы не можете создать. – Icepickle

ответ

0

Поскольку ни один из ваших классов на самом деле не является полиморфным, есть несколько доступных решений.Net встроенных библиотек классов:

Решение 1: JavaScriptSerializer Решение

JavaScriptSerializer позволяет легко переназначить интерфейсы для классов во время десериализации с помощью JavaScriptConverter. Однако не разрешает переименовывать поля и имена свойств, поэтому ваши имена свойств должны соответствовать именам в JSON, которые вы хотите обработать. Следующий конвертер делает трюк:

public class InterfaceToClassConverter<TInterface, TClass> : JavaScriptConverter where TClass : class, TInterface 
{ 
    public InterfaceToClassConverter() 
    { 
     if (typeof(TInterface) == typeof(TClass)) 
      throw new ArgumentException(string.Format("{0} and {1} must not be the same type", typeof(TInterface).FullName, typeof(TClass).FullName)); // Or else you would get infinite recursion! 
     if (!typeof(TInterface).IsInterface) 
      throw new ArgumentException(string.Format("{0} must be an interface", typeof(TInterface).FullName)); 
     if (typeof(TClass).IsInterface) 
      throw new ArgumentException(string.Format("{0} must be a class not an interface", typeof(TClass).FullName)); 
    } 

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) 
    { 
     if (type == typeof(TInterface)) 
      return serializer.ConvertToType<TClass>(dictionary); 
     return null; 
    } 

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 

    public override IEnumerable<Type> SupportedTypes 
    { 
     get 
     { 
      // For an interface-valued property such as "IFoo Foo { getl set; }, 
      // When serializing, JavaScriptSerializer knows the actual concrete type being serialized -- which is never an interface. 
      // When deserializing, JavaScriptSerializer only knows the expected type, which is an interface. Thus by returning 
      // only typeof(TInterface), we ensure this converter will only be called during deserialization, not serialization. 
      return new[] { typeof(TInterface) }; 
     } 
    } 
} 

И использовать это нравится:

public interface IMainInterface 
{ 
    IClass1 @class { get; set; } // NOTICE ALL PROPERTIES WERE RENAMED TO MATCH THE JSON NAMES. 
    List<IClass2> classes { get; set; } 
} 

[Serializable] 
[DataContract(Name = "MainClass")] 
[KnownType(typeof(Class1))] 
[KnownType(typeof(Class2))] 
public class MainClass : IMainInterface 
{ 
    public MainClass() 
    { 
     classes = new List<IClass2>(); 
    } 

    [DataMember(Name = "class")] 
    public IClass1 @class { get; set; } 

    [DataMember(Name = "classes")] 
    public List<IClass2> classes { get; set; } 
} 

[Serializable] 
[DataContract(Name = "class1")] 
public class Class1 : IClass1 
{ 
    public Class1() {} 

    [DataMember(Name = "prop1")] 
    public string prop1 { get; set; } 

    [DataMember(Name = "prop2")] 
    public string prop2 { get; set; } 

    [DataMember(Name = "prop3")] 
    public string prop3 { get; set; } 

} 

[Serializable] 
[DataContract] 
public class Class2 : IClass2 
{ 
    public Class2() { } 

    [DataMember(Name = "propA")] 
    public string propA { get; set; } 

    [DataMember(Name = "propB")] 
    public string propB { get; set; } 

    [DataMember(Name = "propC")] 
    public string propC { get; set; } 
} 

public interface IClass1 
{ 
    string prop1 { get; set; } 
    string prop2 { get; set; } 
    string prop3 { get; set; } 
} 

public interface IClass2 
{ 
    string propA { get; set; } 
    string propB { get; set; } 
    string propC { get; set; } 
} 

public static class TestJavaScriptConverter 
{ 
    public static void Test() 
    { 
     string json = @" 
      { 
      ""class"": 
       {""prop1"":""TestVal0"",""prop2"":""TestVal2"",""prop3"":""TestVal3""}, 
      ""classes"": 
       [ 
        {""propA"":""A"",""propB"":""B"",""propC"":""C""}, 
        {""propA"":""X"",""propB"":""Y"",""propC"":""Z""}, 
        {""propA"":""1"",""propB"":""2"",""propC"":""3""} 
       ] 
      }"; 

     var serializer = new JavaScriptSerializer(); 
     serializer.RegisterConverters(new JavaScriptConverter[] { new InterfaceToClassConverter<IClass1, Class1>(), new InterfaceToClassConverter<IClass2, Class2>() }); 
     var main1 = serializer.Deserialize<MainClass>(json); 
     var json2 = serializer.Serialize(main1); 
     Debug.WriteLine(json2); 
     var main2 = serializer.Deserialize<MainClass>(json2); 

     Debug.Assert([email protected]() == [email protected]()); // No assert 
     Debug.Assert(main1.classes.Select(c => c.ToStringWithReflection()).SequenceEqual(main2.classes.Select(c => c.ToStringWithReflection()))); // no assert 
    } 
} 

Решение 2: DataContractJsonSerializer Решение

WCF и ее DataContractSerializer сек работу только с конкретными типами и не сериализуйте интерфейсы. Таким образом, если вы хотите использовать эти сериализаторы вы должны использовать конкретные классы внутри и представить их с внешним миром, как интерфейсы, например:

public interface IMainInterface 
{ 
    IClass1 Value1 { get; set; } 
    IList<IClass2> Value2 { get; set; } 
} 

[Serializable] 
[DataContract(Name = "MainClass")] 
public class MainClass : IMainInterface 
{ 
    [DataMember(Name = "class")] 
    Class1 RealValue1 { get; set; } 

    [DataMember(Name = "classes")] 
    private List<Class2> RealList2 { get; set; } 

    IList<IClass2> list2Proxy; // can't be readonly because the DataContactJsonSerializer does not call the default constructor. 

    private IList<IClass2> List2Proxy 
    { 
     get 
     { 
      if (list2Proxy == null) 
       Interlocked.CompareExchange(ref list2Proxy, new ConvertingList<Class2, IClass2>(() => this.RealList2, c => c, ToClass), null); 
      return list2Proxy; 
     } 
    } 

    Class2 ToClass(IClass2 iClass) 
    { 
     // REWRITE TO FIT YOUR NEEDS 
     return (Class2)iClass; 
    } 

    Class1 ToClass(IClass1 iClass) 
    { 
     // REWRITE TO FIT YOUR NEEDS 
     return (Class1)iClass; 
    } 

    public MainClass() 
    { 
     RealList2 = new List<Class2>(); 
    } 

    [IgnoreDataMember] 
    public IClass1 Value1 
    { 
     get 
     { 
      return RealValue1; 
     } 
     set 
     { 
      RealValue1 = ToClass(value); 
     } 
    } 

    [IgnoreDataMember] 
    public IList<IClass2> Value2 
    { 
     get 
     { 
      return List2Proxy; 
     } 
     set 
     { 
      if (value == null) 
      { 
       RealList2.Clear(); 
       return; 
      } 
      if (List2Proxy == value) 
       return; 
      RealList2 = value.Select<IClass2, Class2>(ToClass).ToList(); 
     } 
    } 
} 

public class ConvertingList<TIn, TOut> : IList<TOut> 
{ 
    readonly Func<IList<TIn>> getList; 
    readonly Func<TIn, TOut> toOuter; 
    readonly Func<TOut, TIn> toInner; 

    public ConvertingList(Func<IList<TIn>> getList, Func<TIn, TOut> toOuter, Func<TOut, TIn> toInner) 
    { 
     if (getList == null || toOuter == null || toInner == null) 
      throw new ArgumentNullException(); 
     this.getList = getList; 
     this.toOuter = toOuter; 
     this.toInner = toInner; 
    } 

    IList<TIn> List { get { return getList(); } } 

    TIn ToInner(TOut outer) { return toInner(outer); } 

    TOut ToOuter(TIn inner) { return toOuter(inner); } 

    #region IList<TOut> Members 

    public int IndexOf(TOut item) 
    { 
     return List.IndexOf(toInner(item)); 
    } 

    public void Insert(int index, TOut item) 
    { 
     List.Insert(index, ToInner(item)); 
    } 

    public void RemoveAt(int index) 
    { 
     List.RemoveAt(index); 
    } 

    public TOut this[int index] 
    { 
     get 
     { 
      return ToOuter(List[index]); 
     } 
     set 
     { 
      List[index] = ToInner(value); 
     } 
    } 

    #endregion 

    #region ICollection<TOut> Members 

    public void Add(TOut item) 
    { 
     List.Add(ToInner(item)); 
    } 

    public void Clear() 
    { 
     List.Clear(); 
    } 

    public bool Contains(TOut item) 
    { 
     return List.Contains(ToInner(item)); 
    } 

    public void CopyTo(TOut[] array, int arrayIndex) 
    { 
     foreach (var item in this) 
      array[arrayIndex++] = item; 
    } 

    public int Count 
    { 
     get { return List.Count; } 
    } 

    public bool IsReadOnly 
    { 
     get { return List.IsReadOnly; } 
    } 

    public bool Remove(TOut item) 
    { 
     return List.Remove(ToInner(item)); 
    } 

    #endregion 

    #region IEnumerable<TOut> Members 

    public IEnumerator<TOut> GetEnumerator() 
    { 
     foreach (var item in List) 
      yield return ToOuter(item); 
    } 

    #endregion 

    #region IEnumerable Members 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 

    #endregion 
} 

Обратите внимание, что если абонент пытается установить IClass1 или IClass2, который не является фактический Class1 или Class2, InvalidCastException будет выброшен. Таким образом, это приводит к появлению скрытых реализаций интерфейса без реального сохранения реализаций.

+1

Это было очень полезно. Хотя мне пришлось изменить пару вещей в соответствии с моим проектом, но это сработало. – Bhramar

0

Проблема в том, что вы пытаетесь десериализовать класс, который имеет только интерфейсы. Очевидно, что он работает, когда JSON указывает тип __. Но когда это не так (как в вашем втором примере JSON), ReadObject не может автоматически разрешать интерфейсы к их реализации.

Попробуйте использовать конкретные классы Class1, Class2, а не их интерфейсы (IClass1, IClass2). Остальная часть кода может оставаться как есть. Оба ваших примера Json должны отлично работать с этим.

[Serializable] 
[DataContract(Name = "MainClass")] 
[KnownType(typeof(Class1))] 
[KnownType(typeof(Class2))] 
public class MainClass 
{ 

    public MainClass() 
    { 
     Value2 = new List<Class2>(); 
    } 

    [DataMember(Name = "class")] 
    public Class1 Value1 { get; set; } 

    [DataMember(Name = "classes")] 
    public List<Class2> Value2 { get; set; } 

} 
+0

Вопрос заключается в сохранении реферирования «Интерфейс» в классе Это работает, но я хочу структурировать свой класс только с интерфейсом Ref. Спасибо за публикацию, но это мне не помогает. – Bhramar

+0

Bhramar, не понимал, что вы также были ограничены структурой класса. Проблема с интерфейсом (и я уверен, что вы это уже знаете) ... когда типы не указаны, десериализатор не имеет возможности определить, какую реализацию (и их может быть много) использовать. – perNalin

Смежные вопросы