我正在尝试在客户端使用WCF来使用RESTful JSON Web服务。该服务是第三方,因此我无法对服务器响应进行任何更改。
当只有一个数据点时,服务器正在发回一个看起来像这样的响应......
单一数据点
{
"Data":
{
"MyPropertyA":"Value1",
"MyPropertyB":"Value2"
},
}
当有多个数据点时,等等......
多个数据点
{
"Data":
[
{
"MyPropertyA":"Value1",
"MyPropertyB":"Value2"
},
{
"MyPropertyA":"Value3",
"MyPropertyB":"Value4"
},
{
"MyPropertyA":"Value5",
"MyPropertyB":"Value6"
}
],
}
我的服务合同设置如下......
[ServiceContract]
public interface IRewardStreamService
{
[OperationContract]
[WebInvoke]
MyResponse GetMyStuff();
}
和这样的数据点的数据合约......
[DataContract]
public class MyData
{
[DataMember]
public string MyPropertyA { get; set; }
[DataMember]
public string MyPropertyB { get; set; }
}
并且我可以让单个数据点响应工作的唯一方法是,如果我有这样的单个实例属性,但这不解析多个数据点响应......
单个实例的响应
[DataContract]
public class MyResponse
{
[DataMember]
public MyData Data { get; set; }
}
并且我可以让多数据点响应工作的唯一方法是,如果我有这样的数组/列表实例属性,但这不解析单数据点响应......
多个实例的响应
[DataContract]
public class MyResponse
{
[DataMember]
public IList<MyData> Data { get; set; }
}
我理解问题是当只返回一个数据点时,响应省略了括号,但似乎WCF在反序列化该语法时效果不佳。有什么方法可以告诉DataContractJsonSerializer允许单个元素数组不包括括号,然后告诉我的服务使用该序列化器?可能是服务行为还是什么?
任何方向都会有所帮助。
答案 0 :(得分:1)
您可以使用自定义消息格式化程序将JSON的反序列化更改为所需的数据协定。在下面的代码中,数据契约被定义为List<MyData>
;如果响应只包含一个数据点,它将在传递给反序列化器之前将其“包装”到数组中,因此它适用于所有情况。
请注意,我使用JSON.NET库来进行JSON修改,但这不是一个要求(它只有一个很好的JSON DOM来处理JSON文档)。
public class StackOverflow_12825062
{
[ServiceContract]
public class Service
{
[WebGet]
public Stream GetData(bool singleDataPoint)
{
string result;
if (singleDataPoint)
{
result = @"{
""Data"":
{
""MyPropertyA"":""Value1"",
""MyPropertyB"":""Value2""
},
}";
}
else
{
result = @"{
""Data"":
[
{
""MyPropertyA"":""Value1"",
""MyPropertyB"":""Value2""
},
{
""MyPropertyA"":""Value3"",
""MyPropertyB"":""Value4""
},
{
""MyPropertyA"":""Value5"",
""MyPropertyB"":""Value6""
}
],
} ";
}
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
return new MemoryStream(Encoding.UTF8.GetBytes(result));
}
}
[DataContract]
public class MyData
{
[DataMember]
public string MyPropertyA { get; set; }
[DataMember]
public string MyPropertyB { get; set; }
}
[DataContract]
public class MyResponse
{
[DataMember]
public List<MyData> Data { get; set; }
public override string ToString()
{
return string.Format("MyResponse, Data.Length={0}", Data.Count);
}
}
[ServiceContract]
public interface ITest
{
[WebGet]
MyResponse GetData(bool singleDataPoint);
}
public class MyResponseSingleOrMultipleClientReplyFormatter : IClientMessageFormatter
{
IClientMessageFormatter original;
public MyResponseSingleOrMultipleClientReplyFormatter(IClientMessageFormatter original)
{
this.original = original;
}
public object DeserializeReply(Message message, object[] parameters)
{
WebBodyFormatMessageProperty messageFormat = (WebBodyFormatMessageProperty)message.Properties[WebBodyFormatMessageProperty.Name];
if (messageFormat.Format == WebContentFormat.Json)
{
MemoryStream ms = new MemoryStream();
XmlDictionaryWriter jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(ms);
message.WriteMessage(jsonWriter);
jsonWriter.Flush();
string json = Encoding.UTF8.GetString(ms.ToArray());
JObject root = JObject.Parse(json);
JToken data = root["Data"];
if (data != null)
{
if (data.Type == JTokenType.Object)
{
// single case, let's wrap it in an array
root["Data"] = new JArray(data);
}
}
// Now we need to recreate the message
ms = new MemoryStream(Encoding.UTF8.GetBytes(root.ToString(Newtonsoft.Json.Formatting.None)));
XmlDictionaryReader jsonReader = JsonReaderWriterFactory.CreateJsonReader(ms, XmlDictionaryReaderQuotas.Max);
Message newMessage = Message.CreateMessage(MessageVersion.None, null, jsonReader);
newMessage.Headers.CopyHeadersFrom(message);
newMessage.Properties.CopyProperties(message.Properties);
message = newMessage;
}
return this.original.DeserializeReply(message, parameters);
}
public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
{
throw new NotSupportedException("This formatter only supports deserializing reply messages");
}
}
public class MyWebHttpBehavior : WebHttpBehavior
{
protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
IClientMessageFormatter result = base.GetReplyClientFormatter(operationDescription, endpoint);
if (operationDescription.Messages[1].Body.ReturnValue.Type == typeof(MyResponse))
{
return new MyResponseSingleOrMultipleClientReplyFormatter(result);
}
else
{
return result;
}
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new WebHttpBinding(), new EndpointAddress(baseAddress));
factory.Endpoint.Behaviors.Add(new MyWebHttpBehavior());
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.GetData(false));
Console.WriteLine(proxy.GetData(true));
Console.Write("Press ENTER to close the host");
((IClientChannel)proxy).Close();
factory.Close();
Console.ReadLine();
host.Close();
}
}
答案 1 :(得分:0)
我不知道如何使用WCF,所以我将更改为Asp.Net WCF。这篇文章将为您提供一个方式
http://www.west-wind.com/weblog/posts/2012/Aug/30/Using-JSONNET-for-dynamic-JSON-parsing
我无法弄清楚如何确定它是数组还是单个对象。这是一个小代码。
[TestMethod]
public void SingleObject()
{
using (var client = new HttpClient())
{
var result = client.GetStringAsync("http://localhost:8080/api/JSONTestOne");
string content = result.Result;
JObject jsonVal = JObject.Parse(content);
dynamic aFooObj = jsonVal;
Console.WriteLine(aFooObj.afoo.A);
}
}
[TestMethod]
public void ArrayWithObject()
{
using (var client = new HttpClient())
{
var result = client.GetStringAsync("http://localhost:8080/api/JSONTest");
string content = result.Result;
JObject jsonVal = JObject.Parse(content);
dynamic foos = jsonVal;
Console.WriteLine(foos[0].A);
}
}