Make ASP.NET WCF convert dictionary to JSON, omitting "Key" & "Value" tags
Asked Answered
G

5

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

Glum answered 28/9, 2011 at 21:54 Comment(0)
S
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] );
        }
    }
Sliest answered 28/9, 2011 at 22:5 Comment(11)
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.Glum
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: #627856Sliest
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).Glum
did you try adding DataContractAttribute to the class and DataMemberAttribute to Members of the objects being serialized??Sarabia
@MarkisT Do you know why the GetObjectData would not be called by WCF? I have a breakpoint on it, but it is never hit when I invoke the service method with a data parameter that is the serialisable dictionary.Bedel
Just realised that I am thinking of the deserialisation (in to the method) rather than serialising (the return out of the method). Doh!Bedel
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.Chud
@MarkisT How ca in take the same as input in wcf rest?Related
Sorry, it has been a really long time since I wrote this answer. I am not sure how to deserialize. Hopefully someone else can answer that.Sliest
@UnknownUser A little late but I've posted an answer that explains how to add deserialization (https://mcmap.net/q/436773/-make-asp-net-wcf-convert-dictionary-to-json-omitting-quot-key-quot-amp-quot-value-quot-tags)Lovieloving
excuse me. i'm new to dotnet and currently working on a legacy code. my question is where to add this code? i've created a class and put this into it but not worked for me.Lockard
L
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)));
     }
}
Lovieloving answered 8/11, 2017 at 18:46 Comment(0)
F
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

Facelifting answered 20/10, 2014 at 9:46 Comment(0)
P
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).

Profitable answered 30/12, 2014 at 4:38 Comment(1)
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.Cent
S
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: https://mcmap.net/q/451567/-posting-a-collection-of-subclasses
        //config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
Segno answered 3/3, 2014 at 23:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.