JSON 不适用于 Web 服务中的类型化类
我有一个如下所示的类:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
[DataContract()]
public class TestCol : List<Test> { }
[DataContract()]
public class MainTest
{
public TestCol Components { get; set; }
}
[DataContract()]
public class Test
{
public Test() { }
public String Name { get; set; }
}
以及一个具有以下 webmethod 的 Web 服务:
[WebMethod]
public String Test(MainTest input)
{
String rtrn = String.Empty;
foreach (Test test in input.Components)
rtrn += test.Name;
return rtrn;
}
由 AJAX 使用以下方法调用:
var Test = {};
Test.Name = "Test";
var MainTest = {};
MainTest.Components = [];
MainTest.Components.push(Test);
$.ajax({
type: "POST",
url: "WebService/WSTest.asmx/Test",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({
"input": MainTest
}),
success: function(data, textStatus) {
console.log("success");
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
window.console && console.log && console.log(XMLHttpRequest.responseText + " || " + textStatus + " || " + errorThrown);
}
});
当执行 AJAX 调用时,它将返回错误。我发现错误出在类型化类 TestCol
上,它没有属性。 现在我找到了 2 个需要更改 C# 类的解决方案:
删除
TestCol
类并将Components
属性更改为List
< /code> 数据类型: <前><代码>[DataContract()] 公共类MainTest { 公共列表<测试>组件{获取;放; } } [数据契约()] 公开课测试 { 公共测试(){} 公共字符串名称{获取;放; } }
或添加
<前><代码>[DataContract()] 公共类 TestCol : ListTestCol
类的额外属性并更改 webmethod:; { 公共列表<测试>组件{获取;放; } } [数据契约()] 公共类MainTest { 公共 TestCol 组件 { 获取;放; } } [数据契约()] 公开课测试 { 公共测试(){} 公共字符串名称{获取;放; } } &
<前><代码>[WebMethod] 公共字符串测试(MainTest 输入) { 字符串 rtrn = String.Empty; foreach(在input.Components.Components中测试测试) rtrn += 测试.名称; 返回rtrn; }
这两种解决方案都需要更改 C# 类,但我不喜欢这样做,因为其他代码依赖于它。 有人知道这个问题的解决方案吗?
编辑:我已经上传了一个测试解决方案,其中包含上述代码:http://jeroenvanwarmerdam.nl/content/temp/JSONtoClassWebservice.zip
I'm having a class like the following:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
[DataContract()]
public class TestCol : List<Test> { }
[DataContract()]
public class MainTest
{
public TestCol Components { get; set; }
}
[DataContract()]
public class Test
{
public Test() { }
public String Name { get; set; }
}
And a webservice with the following webmethod like this:
[WebMethod]
public String Test(MainTest input)
{
String rtrn = String.Empty;
foreach (Test test in input.Components)
rtrn += test.Name;
return rtrn;
}
Which is called by AJAX with the following method:
var Test = {};
Test.Name = "Test";
var MainTest = {};
MainTest.Components = [];
MainTest.Components.push(Test);
$.ajax({
type: "POST",
url: "WebService/WSTest.asmx/Test",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({
"input": MainTest
}),
success: function(data, textStatus) {
console.log("success");
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
window.console && console.log && console.log(XMLHttpRequest.responseText + " || " + textStatus + " || " + errorThrown);
}
});
When executing the AJAX call, it will return errors. I found out that the error is with the typed class TestCol
, which has no properties.
Now do I have found 2 solutions that require changes in the C# classes:
Remove the
TestCol
class and change theComponents
property toList<Test>
datatype:[DataContract()] public class MainTest { public List<Test> Components { get; set; } } [DataContract()] public class Test { public Test() { } public String Name { get; set; } }
Or add an extra property to the
TestCol
class and change the webmethod:[DataContract()] public class TestCol : List<Test> { public List<Test> Components { get; set; } } [DataContract()] public class MainTest { public TestCol Components { get; set; } } [DataContract()] public class Test { public Test() { } public String Name { get; set; } }
&
[WebMethod] public String Test(MainTest input) { String rtrn = String.Empty; foreach (Test test in input.Components.Components) rtrn += test.Name; return rtrn; }
Both solutions require changes in the C# classes, which I prefer not to, as other code is depended on it. Does anyone know a solution for this problem?
Edit: I've uploaded a test solution, containing above code: http://jeroenvanwarmerdam.nl/content/temp/JSONtoClassWebservice.zip
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
如果您使用 ASMX 服务,则 JavaScriptSerializer 将负责数据转换,而不是 DataContractJsonSerializer。因此,您使用的所有
DataContract
属性都将不起作用。您编写了诸如
public class TestCol : List这样的类; { }
对 不利JavaScriptSerializer,但是以List
作为属性的类(public class MainTest { public ListComponents { get; set; }}
)有没问题。所以我建议将代码简化为以下内容。用作参数的类可以定义为
WebMethod
Test
,ajax 调用可以是
您如何看到所有内容都将非常简单并且有效。有关如何发送复杂数据的更多详细信息,我建议您阅读 另一个答案 和 这个。
If you use ASMX services the JavaScriptSerializer will be responsible for the data conversion and not the DataContractJsonSerializer. So all
DataContract
attributes which you use will not work.You are write that classes like
public class TestCol : List<Test> { }
are bad for the JavaScriptSerializer, but classes havingList<Test>
as the property (public class MainTest { public List<Test> Components { get; set; }}
) have no problem.So I suggest to simplify you code to the following. The classes used as the parameters can be defines as
The WebMethod
Test
will beand the ajax call can be
How you can see all will be very simple and it's work. For more details how you can send complex data I recommend you to read another answer and this.
您似乎正在使用 ASMX (而不是 WCF),因为您在所有公共属性上省略了
[DataMember]
属性,但仍然被序列化。 WCF 是“选择加入”的,因此您不应该看到任何正确的序列化。因此,所有
[DataContract]
属性都是无用的。如果您使用 ScriptManger 并输出 JSON,ASMX 默认为 JavaScriptSerializer。 JavaScriptSerializer 是“选择退出”的(这意味着所有公共属性都会自动序列化,除非用
[ScriptIgnoreAttribute]
标记)。JavaScriptSerializer 支持序列化 List<>。您应该不会在序列化 TestCol 属性时遇到问题,因为 JavaScriptSerializer 自动支持序列化所有实现 IEnumerable(但不包括 IDictionary)的类型——其中包括 List<> -- 转换为 JSON 数组。
您的错误似乎是 JavaScriptSerializer 无法正确处理从 List继承 的类(或者来自实现 IEnumerable 的类)。在第一个解决方法中,您消除了从 List<> 继承的类。在第二个解决方法中,您跳过了基类的所有功能,但重新实现了 List<> 。在一个属性中。
您的 JSON 发布数据当前如下所示:
但是,您在序列化程序中还有一个额外的级别或重定向(继承自
List
->TestCol
)。序列化程序可能正在寻找:因为您本质上是序列化 List<> 的“Items”属性。因此,您的 JSON post 数据只是将 Test 对象提供到错误的位置,并且您的 TestCol Components 属性最终为空。
我建议您添加一个 Web 服务方法来输出测试 MainTest 对象,以查看它序列化成的内容。您可能会发现它增加了一个额外的级别。
You seem to be using ASMX (not WCF) because you have omitted
[DataMember]
attributes on all your public properties and still get serialized. WCF is "opt-in", so you shouldn't be seeing any serialization of any properly.As a result, all
[DataContract]
attributes are useless.ASMX defaults to the JavaScriptSerializer if you are using ScriptManger and outputing JSON. The JavaScriptSerializer is "opt-out" (which means that all public properties are automatically serialized unless marked with
[ScriptIgnoreAttribute]
).The JavaScriptSerializer supports serializing List<>'s. You should not be having problems serializing your TestCol property because JavaScriptSerializer automatically supports serializing all types that implement IEnumerable (but not IDictionary) -- which includes List<> -- into JSON arrays.
Your error seems to be that the JavaScriptSerializer does not properly handle classes that inherit from List<> (or from a class implementing IEnumerable). In your first work-around, you eliminated the class that inherited from List<>. In your second work-around, you skipped all functionalities of the base class, but re-implemented the List<> in a property.
Your JSON post data currently looks like:
However, you have one extra level or redirection in the serializer (inheriting from
List<Test>
->TestCol
). It is possible that the serializer is looking for:because you are essentially serializing the "Items" property of List<>. So, your JSON post data is just feeding Test objects to the wrong place, and your TestCol Components property ends up empty.
I'd suggest that you add a web service method to output a test MainTest object to see what it serializes into. You'll probably find that it puts in an additional level.
所以这个解决方案将List更改为Object而不是Test。我希望更改尽可能少的代码(我不喜欢在 foreach 循环中进行强制转换)。下面的代码通过添加两个函数和前面提到的继承更改来实现此目的。
转换函数由 TurBas 提供
将对象映射到字典,反之亦然
So this solution changes the List to Object instead of Test. I hoped to change as little code as possible (i dislike having to do casts in foreach loops). The below code does so with two function additions and the previously mentioned inheritance change.
Convert function courtesy TurBas
Mapping object to dictionary and vice versa
JavaScriptSerializer序列化:IEnumerable -> JavaScript Array
当使用 JavaScriptSerializer 时,它会自动转换 IEnumerable(不带 IDictionary)类型——涵盖 List<> 类型。或从它派生的任何东西——放入数组中。
反序列化:JavaScript 数组 -> IEnumerable -> IEnumerable ->集合对象
现在,在从 JSON 进行反序列化时,JavaScriptSerializer 必须获取数组,创建一个 IEnumerable,然后通过将该 IEnumerable 传递到其构造函数来为该字段创建一个对象。
现在通过构造函数构造 Collection 对象
,对于 List<>您有一个采用 IEnumerable 的构造函数重载。因此,如果您将
List
作为组件的类型,它会创建得很好。构造函数不是继承的
但是,TestCol 确实没有有这样的构造函数!它与
List
配合使用而不是与TestCol
(派生自List
)配合使用的原因是,唯一类之间不继承的是构造函数!因此,JavaScriptSerializer 没有任何方法从 IEnumerable 构造 TestCol。所以它默默地失败了。
通过创建列表反序列化数组,然后转换为类型
现在 JavaScriptSerializer 可能会尝试从此
IEnumerable
创建一个List
,然后尝试转换它进入 TestCol。可能的解决方案
解决方案: 尝试放入:
作为 TestCol 的构造函数。
如果仍然不起作用,请实现从
List
到TestCol
的显式类型转换。JavaScriptSerializer serialization: IEnumerable -> JavaScript Array
When the JavaScriptSerializer is used, it automatically converts an IEnumerable (without IDictionary) type -- that covers List<> or anything derived from it -- into an array.
Deserialization: JavaScript Array -> IEnumerable -> Collection Object
Now, upon deserialization from JSON, the JavaScriptSerializer must take the array, create an IEnumerable, then create an object for the field by passing that IEnumerable into its constructor.
Constructing Collection object via Constructor
Now, for List<> you have a constructor overload that takes an IEnumerable. So if you put
List<Test>
as the type of your component it creates it fine.Constructors not inherited
However, TestCol does NOT have such a constructor! The reason why it worked with
List<Test>
and not withTestCol
(which derives fromList<Test>
) is that the only thing that is not inherited between classes are constructors!Therefore, the JavaScriptSerializer does not have any way to construct a TestCol from an IEnumerable. So it fails silently.
Deserialize Array by Creating List, then Casting to Type
Now the JavaScriptSerializer may then attempt to create a
List<Test>
from thisIEnumerable<Test>
, and then try to cast it into a TestCol.Possible Solution
Solution: Try putting in:
as your TestCol's constructors.
And if it still doesn't work, implement an explicit type cast from
List<Test>
toTestCol
.嗯,这在网络方法中不起作用?
下面再评论一下,这行得通吗?
它应该有效,因为可以枚举一个类......
hmmm this didn't work in the web method?
Re comment below, does this work then?
It should work because a class can be enumerated...
如果您需要 JSON,则需要返回 JSON。
检查 System.Web.Script.Serialization.JavaScriptSerializer
http://msdn.microsoft.com/en -us/library/system.web.script.serialization.javascriptserializer.aspx
If you're expecting JSON, you'll need to return JSON.
Check with the System.Web.Script.Serialization.JavaScriptSerializer
http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx