Search code examples
c#jsonwcf

Serialize C# Dictionary as JSON object using WCF with HTTP


I'm new to WCF services with HTTP Routing, but my new project uses WCF. The services return objects marked with with [DataContract] and [DataMember] attributes returned as part of services marked with [ServiceContract] and [OperationContract] attributes

I want to return a class with a Dictionary<string, string> like this:

var myObject = new Dictionary<string, string> { { "aKey", "aValue" } };

I want it to JSON serialize like this:

{
  "aKey": "aValue"
}

But it serializes like this:

[{
  "Key": "aKey",
  "Value": "aValue"
}] 

How can I do that with WCF + HTTP?

I found many helpful answers, but they don't show me how to do it in the WCF/HTTP context so REST responses are automatically JSON serialized

EDIT


Solution

  • I tried an approach from @dbc's first suggestion, by creating my own class which extends ISerializable. See my modified class below.. Right now that doesn't work for me, the GetObjectData method is never called, at least my debug breakpoint doesn't hit it. That suggests maybe my project is not using a default serializer (?)

    [Serializable]
    public class SerializableDictionary<TKey, TValue> : ISerializable, IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable
    {
        //https://stackoverflow.com/a/7590189/1175496
        private Dictionary<TKey, TValue> _Dictionary;
        public SerializableDictionary(Dictionary<TKey, TValue> dictionary)
        {
            _Dictionary = dictionary;
        }
        public SerializableDictionary() : this(new Dictionary<TKey, TValue>()) { }
    
        public SerializableDictionary(SerializationInfo info, StreamingContext context) : this(new Dictionary<TKey, TValue>()) {
        /* no deserializing */
        //https://stackoverflow.com/a/47187399/1175496
        }
    
    
        public TValue this[TKey key]
        {
            get { return _Dictionary[key]; }
            set { _Dictionary[key] = value; }
        }
    
        public ICollection<TKey> Keys => this._Dictionary.Keys;
    
        public ICollection<TValue> Values => this._Dictionary.Values;
    
        public int Count => this._Dictionary.Count;
    
        public bool IsReadOnly => true;
    
        public void Add(TKey key, TValue value)
        {
            _Dictionary.Add(key, value);
        }
    
        public void Add(KeyValuePair<TKey, TValue> item)
        {
            throw new NotImplementedException();
        }
    
        public void Clear()
        {
            throw new NotImplementedException();
        }
    
        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            throw new NotImplementedException();
        }
    
        public bool ContainsKey(TKey key)
        {
            throw new NotImplementedException();
        }
    
        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }
    
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return _Dictionary.GetEnumerator();
        }
    
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            foreach (TKey key in _Dictionary.Keys)
                info.AddValue(key.ToString(), _Dictionary[key]);
        }
    
        public bool Remove(TKey key)
        {
            throw new NotImplementedException();
        }
    
        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            throw new NotImplementedException();
        }
    
        public bool TryGetValue(TKey key, out TValue value)
        {
            return this._Dictionary.TryGetValue(key, out value);
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this._Dictionary.GetEnumerator();
        }
    }
    

    Instead I tried the approach from @dbc's second suggestion, and it worked. It relies on returning a System.ServiceModel.Channels.Message manually, as shown below so I don't love it.

    I'd prefer to use DataContractJsonSerializerSettings.UseSimpleDictionaryFormat as @dbc suggests, but it's not exposed, as @dbc says!). So for now, this may be the best way. If there are no other suggestions, eventually I'll close the question as duplicate or something... Thanks, @dbc!

        [OperationContract]
        [WebGet( UriTemplate = "/getResponse", ResponseFormat = WebMessageFormat.Json)]
        public Message GetResponse()
        {
    
            string json = JsonConvert.SerializeObject(new Dictionary<string, string> { { "key", "value" } });
            return WebOperationContext.Current.CreateTextResponse(json, "application/json; charset=utf-8", Encoding.UTF8);
    
        }