34

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! :)

5 Answers 5

48

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] );
        }
    }
Sign up to request clarification or add additional context in comments.

11 Comments

Wow! That's very close to being exactly what I need... just one thing. Is there a way to not include the "__type" key/value pair? I'd rather people using this didn't see that value, but I can't figure out how to get rid of it.
Oh yeah, I can't remember if that was fixed with .NET 4.0 or if there was something else I did to get rid of that. I also googled and found this which might work as well: stackoverflow.com/questions/627356/…
Unfortunately, I can't give you an upvote because I don't have enough reputation - I'll mark this as best answer though, because it's the closest to what I need (I just wasn't able to get rid of the __type thing).
Somehow this example doesn't work for me. Doing a GET will result in a 'no response' but I see with the debugger that GetObjectData is called.
@MarkisT How ca in take the same as input in wcf rest?
|
5

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

Comments

2

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

Comments

2

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).

1 Comment

Nice. Similar to this answer -- that is, it manually serializes and returns "raw content". Would upvote but I ran out of votes for the day.
0

avoiding the "__type" in json...

in the webapi.config, there are several options (look to the last one):

        // To disable tracing in your application, please comment out or remove the following line of code
        // For more information, refer to: http://www.asp.net/web-api
        //config.EnableSystemDiagnosticsTracing();

        // Use camel case for JSON data.
        config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

        // The setting will let json.net to save type name in the payload if the runtime type is different with the declare type. 
        // When you post it back, json.net will deserialize the payload to the type you specified in the payload.
        // source: http://stackoverflow.com/questions/12858748/asp-net-webapi-posting-collection-of-subclasses
        //config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.