Make ASP.NET WCF convert dictionary to JSON, omitting "Key" & "Value" tags
Here's my dilemma. I'm using a RESTful ASP.NET service, trying to get a function to return a JSON string in this format:
{"Test1Key":"Test1Value","Test2Key":"Test2Value","Test3Key":"Test3Value"}
But I'm getting it in this format instead:
[{"Key":"Test1Key","Value":"Test1Value"},
{"Key":"Test2Key","Value":"Test2Value"},
{"Key":"Test3Key","Value":"Test3Value"}]
My method looks like this:
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public Dictionary<string, string> Test(String Token)
{
if (!IsAuthorized(Token))
return null;
if (!IsSecure(HttpContext.Current))
return null;
Dictionary<string, string> testresults = new Dictionary<string, string>();
testresults.Add("Test1Key", "Test1Value");
testresults.Add("Test2Key", "Test2Value");
testresults.Add("Test3Key", "Test3Value");
return testresults;
}
Is there any way I can get rid of those "Key" and "Value" tags using only built in ASP.NET tools? (i.e., I'd rather not use JSON.NET, if it's avoidable)
Thanks very much! :)
The .NET dictionary class won't serialize any other way than the way you described. But if you create your own class and wrap the dictionary class then you can override the serializing/deserializing methods and be able to do what you want. See example below and pay attention to the "GetObjectData" method.
[Serializable]
public class AjaxDictionary<TKey, TValue> : ISerializable
{
private Dictionary<TKey, TValue> _Dictionary;
public AjaxDictionary()
{
_Dictionary = new Dictionary<TKey, TValue>();
}
public AjaxDictionary( SerializationInfo info, StreamingContext context )
{
_Dictionary = new Dictionary<TKey, TValue>();
}
public TValue this[TKey key]
{
get { return _Dictionary[key]; }
set { _Dictionary[key] = value; }
}
public void Add(TKey key, TValue value)
{
_Dictionary.Add(key, value);
}
public void GetObjectData( SerializationInfo info, StreamingContext context )
{
foreach( TKey key in _Dictionary.Keys )
info.AddValue( key.ToString(), _Dictionary[key] );
}
}
Expanding slightly on @MarkisT's excellent solution, you can modify the serialization constructor to recreate one of these dictionaries from the same JSON (thus allowing you to take an AjaxDictionary as a service parameter), as follows:
public AjaxDictionary( SerializationInfo info, StreamingContext context )
{
_Dictionary = new Dictionary<TKey, TValue>();
foreach (SerializationEntry kvp in info)
{
_Dictionary.Add((TKey)Convert.ChangeType(kvp.Name, typeof(TKey)), (TValue)Convert.ChangeType(kvp.Value, typeof(TValue)));
}
}
In case anyone has that problem on the client side: conversion from that weird {Key: "x", Value:"y"} Array to a { x: "y" } object can be done in a single line of JS:
var o = i.reduce(function (p, c, a, i) { p[c.Key] = c.Value; return p }, {});
with i being the array returned from the service, and o being what you actually want.
best regards
I ran up against this problem a number of months ago and posted a somewhat less-than-optimally succinct question here: Configuring WCF data contract for proper JSON response
The problem I had back then turned out to be same as the much more precisely posted question here, in short: within the context of WCF the standard asp.net serialization tools will, for a dictionary, return an ARRAY rather than a key/value pair json OBJECT. I am posting my solution which worked for me although I did resort to using JSON.NET (which I realize the poster was trying to avoid). Nevertheless, maybe this will be helpful to someone.
Function myDictionaryFunction () As Stream Implements IMywebservice.myDictionaryFunction
Dim myKeyValuePairObject As Object = New Dynamic.ExpandoObject
Dim myDictionary = DirectCast(myKeyValuePairObject, IDictionary(Of String, Object))
myDictionary.Add("Test1Key", "Test1Value")
myDictionary.Add("Test2Key", "Test2Value")
myDictionary.Add("Test3Key", "Test3Value")
strJson = JsonConvert.SerializeObject(myKeyValuePairObject)
Dim resultBytes As Byte() = Encoding.UTF8.GetBytes(strJson)
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain"
Return New MemoryStream(resultBytes)
End Function
The result:
{"Test1Key":"Test1Value","Test2Key":"Test2Value","Test3Key":"Test3Value"}
The expando object works like a charm. But to make it work you have to force WCF to return plain text which one would think is easy but it is not. You have to implement a RawContentTypeMapper as suggested here: http://referencesource.microsoft.com/#System.ServiceModel.Web/System/ServiceModel/Channels/RawContentTypeMapper.cs ...And then you have to mess around with your web.config file something like this:
<customBinding>
<binding name="RawReceiveCapable">
<webMessageEncoding
webContentTypeMapperType="myNamespace.RawContentTypeMapper, myLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<httpTransport manualAddressing="true" maxReceivedMessageSize="524288000" transferMode="Streamed" />
</binding>
</customBinding>
I am the first to admit that this solution will likely not receive any awards for elegance. But it worked and returning raw content from a WCF webservice will, if needed, give you some extra control how to serialize your WCF data payload. Since implementing this, I have migrated more and more to ASP.NET Web API (which makes returning RESTful anything much easier than WCF, IMO).