简体   繁体   English

使用DataContractJsonSerializer,将JSON字符串反序列化为具有列表和接口作为属性的C#对象

[英]Using DataContractJsonSerializer, deserialization of JSON string into C# object which has list & interface as properties

I am working on ac#.dotNet project which invokes a 3rd party REST service. 我正在研究ac#.dotNet项目,该项目将调用第三方REST服务。

Sample Class Structure : 示例类结构:

[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 : (with type hint) 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 : (without type hint) 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"}
    ]
}

So, for given class structure if I generate json (of object of MainClass ) using DataContractJsonSerializer , I am getting Json_String1 and if i directly deserialize, it works fine. 因此,对于给定的类结构,如果我使用DataContractJsonSerializer生成JSON( MainClass对象的对象),则得到Json_String1 ,如果我直接反序列化,则可以正常工作。

Whereas as while GET ting data, response is Json_String2 ( w/o type hint). 尽管在获取数据时,响应是Json_String2 (不带类型提示)。 Hence, while deserializing I get following error. 因此,在反序列化时出现以下错误。

无效的强制转换例外

InvalidCastException was unhandled. 未处理InvalidCastException。 Unable to cast object of type 'System.Object' to type 'WpfApplication1.IClass2'. 无法将类型为“ System.Object”的对象转换为类型为“ WpfApplication1.IClass2”。

Now, I manually have to modify above json (string manipulation) by adding type hint, to deserialize it successfully. 现在,我必须通过添加类型提示手动修改上面的json(字符串操作),以成功反序列化它。

Question 1) how can I avoid this Json String Manipulation for deserializing ? 问题1)如何避免反序列化此Json String Manipulation?

Question 2) how can I create json without type hint ? 问题2)如何在没有类型提示的情况下创建json?

edit : 1. Added IMainInterface which is implemented by MainClass . 编辑 :1.增加IMainInterface这是由MainClass实现。 2. dotNet Framework 4 2. dotNet Framework 4

The issue is because you're trying to deserialize to a class that only has interfaces. 问题是因为您试图反序列化为仅具有接口的类。 It obviously works when the JSON specifies the __type. 当JSON指定__type时,它显然可以工作。 But when it doesn't (like in your second JSON example), ReadObject isn't able to automatically resolve the interfaces to their implementation. 但是,当它没有这样做时(例如在您的第二个JSON示例中),ReadObject无法自动将接口解析为其实现。

Try using the concrete classes Class1 , Class2 in MainClass instead of their interfaces (IClass1, IClass2). 尝试在MainClass使用具体的类Class1Class2代替其接口(IClass1,IClass2)。 The rest of the code can remain as-is. 其余代码可以保持原样。 Both your Json examples should work fine with this. 您的两个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; }

}

Since none of your classes are actually polymorphic, there are a couple solutions available that use .Net built-in class libraries: 由于您的类实际上都不是多态的,因此有几种使用.Net内置类库的解决方案:

Solution 1: JavaScriptSerializer Solution 解决方案1: JavaScriptSerializer解决方案

JavaScriptSerializer makes it easy to remap interfaces to classes during deserialization by using a JavaScriptConverter . JavaScriptSerializer可以很容易地通过使用JavaScriptConverter在反序列化期间将接口重新映射到类。 However, it does not allow field and property names to be remapped , thus your property names must match the names in the JSON you wish to process . 但是, 它不允许重新映射字段和属性名称 ,因此您的属性名称必须与您要处理的JSON中的名称匹配 The following converter does the trick: 以下转换器可以解决问题:

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

And use it like: 并像这样使用它:

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(main1.@class.ToStringWithReflection() == main2.@class.ToStringWithReflection()); // No assert
        Debug.Assert(main1.classes.Select(c => c.ToStringWithReflection()).SequenceEqual(main2.classes.Select(c => c.ToStringWithReflection()))); // no assert
    }
}

Solution 2: DataContractJsonSerializer Solution 解决方案2: DataContractJsonSerializer解决方案

WCF and its DataContractSerializer s work only with concrete types and do not serialize interfaces. WCF及其DataContractSerializer 仅适用于具体类型,并且不序列化接口。 Thus, if you wish to use these serializers you must use concrete classes internally and present them to the outside world as interfaces, for instance: 因此,如果您希望使用这些序列化器,则必须在内部使用具体的类并将其作为接口呈现给外部世界,例如:

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
}

Note that if a caller attempts to set an IClass1 or IClass2 which is not an actual Class1 or Class2 , an InvalidCastException will be thrown. 请注意,如果调用者尝试设置不是实际Class1Class2IClass1IClass2 ,则将引发InvalidCastException Thus this gives the appearance of hiding interface implementations without truly keeping the implementations private. 因此,这看上去隐藏了接口实现,而没有真正保持实现私有。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 C# 中使用 DataContractJsonSerializer 使用 List&lt;&gt; 反序列化 JSON 对象 - Deserialization of JSON object with List<> using DataContractJsonSerializer in C# 使用C#中的DataContractJsonSerializer将子字段作为字符串反序列化JSON对象 - Deserialization of JSON object with sub field as string using DataContractJsonSerializer in C# 在C#中使用DataContractJsonSerializer反序列化JSON对象 - Deserialization of JSON object by using DataContractJsonSerializer in C# 使用DataContractJsonSerializer对JSON对象进行部分反序列化 - Partial deserialization of JSON object by using DataContractJsonSerializer 使用DataContractJsonSerializer将Json反序列化为C#动态对象 - Deserialize Json into a C# dynamic object using DataContractJsonSerializer C#-使用DataContractJsonSerializer将JSON字符串反序列化为Enum [] - C# - Deserialize JSON string to Enum[] using DataContractJsonSerializer JSON字符串反序列化到C#列表中 - JSON string Deserialization into C# List 在 C# 中反序列化期间,如何防止初始化 JSON 字符串中不存在的属性? - How to prevent initialization of properties which are not present in JSON string during Deserialization in C#? C#API调用:JSON字符串转换为类属性(反序列化等) - C# API Call: JSON String to Class Properties (Deserialization and more) 使用DataContractJsonSerializer添加反斜杠序列化具有Web URL到JSon的字符串 - Serializing string which has web url to JSon using DataContractJsonSerializer adding backslashes
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM