Generic WCF JSON Deserialization
Asked Answered
P

5

14

I am a bit new to WCF and will try to clearly describe what I am trying to do.

I have a WCF webservice that uses JSON requests. I am doing fine sending/receiving JSON for the most part. For example, the following code works well and as expected.

JSON sent:

{ "guy": {"FirstName":"Dave"} }

WCF:

    [DataContract]
    public class SomeGuy
    {
        [DataMember]
        public string FirstName { get; set; }
    }

    [OperationContract]
    [WebInvoke(Method = "POST",
               BodyStyle = WebMessageBodyStyle.WrappedRequest,
               RequestFormat = WebMessageFormat.Json,
               ResponseFormat = WebMessageFormat.Json)]
    public string Register(SomeGuy guy)
    {
        return guy.FirstName;
    }

This returns a JSON object with "Dave" as expected. The problem is that I cannot always guarantee that the JSON I recieve will exactly match the members in my DataContract. For example, the JSON:

{ "guy": {"firstname":"Dave"} }

will not serialize correctly because the case does not match. guy.FirstName will be null. This behavior makes sense, but I don't really know how to get around this. Do I have to force the field names on the client or is there a way I can reconcile on the server side?

A possibly related question: can I accept and serialize a generic JSON object into a StringDictionary or some kind of simple key value structure? So no matter what the field names are sent in the JSON, I can access names and values that have been sent to me? Right now, the only way I can read the data I'm receiving is if it exactly matches a predefined DataContract.

Pointing answered 19/2, 2010 at 16:32 Comment(2)
Of course you can guarantee that the json is conformant to the contract. Only if you don't have control of the client could you not be able to control it. If you don't have control of the client then it is probably outside the domain of your service and the point is moot. I don't see the problem. All the workarounds I see are futile and will just add pain. Read the specs on DataContract/DataMember and follow them. 2 pesos.Variant
You're right. Technically, I could write it into the JS before the info gets sent. And, I hinted in a different comment that this is probably what I will do in the end. Though, I would like a service to be able to handle an arbitrary number of fields that are not necessarily known beforehand. Does that make sense? I just want a service that can accept a completely arbitrary list of "key": "value" pairs and then decide what do do with them without having to know the "key" names beforehand.Pointing
G
11

Here's an alternative way to read json into dictionary:

[DataContract]
public class Contract
    {
    [DataMember]
    public JsonDictionary Registration { get; set; }
    }

[Serializable]
public class JsonDictionary : ISerializable
    {
    private Dictionary<string, object> m_entries;

    public JsonDictionary()
        {
        m_entries = new Dictionary<string, object>();
        }

    public IEnumerable<KeyValuePair<string, object>> Entries
        {
        get { return m_entries; }
        }

    protected JsonDictionary(SerializationInfo info, StreamingContext context)
        {
        m_entries = new Dictionary<string, object>();
        foreach (var entry in info)
            {
            m_entries.Add(entry.Name, entry.Value);
            }
        }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
        foreach (var entry in m_entries)
            {
            info.AddValue(entry.Key, entry.Value);
            }
        }
    }
Goglet answered 9/9, 2011 at 10:57 Comment(1)
+1. Similar solution is also found here: social.msdn.microsoft.com/Forums/en-US/wcf/thread/…Inositol
E
5

As the name implies, a data contract is a set of rules. If you want to reliably map messages to operations, then those rules need to be followed.

Why can't you guarantee that the casing will be correct? If you just want to use lowercase identifiers from JavaScript instead, you can use the MessageParameter attribute for that - but you still have to choose a specific name.

In theory you could accept raw JSON and deserialize it manually (just take a string parameter and use any JSON library for deserialization), but that is really not in the spirit of WCF.

I think what you really need to fix is not the fact that the data contract is case sensitive, but the fact that the JSON isn't being put together correctly at the client side.


If you want to accept the raw JSON string in your operation, then change the BodyStyle to WebMessageBodyStyle.Bare, and change your method signature to accept a single string parameter, which will be populated with whatever JSON string was sent by the client.

Note that all you get is a single string out of this, you have to do all the parsing and property mapping and validation and error handling yourself. It's not the route I would choose to take, but if you're willing to take on the effort and risk involved, it's one potential option.

Escarp answered 19/2, 2010 at 16:52 Comment(2)
"what you really need to fix is [..] the fact that the JSON isn't being put together correctly at the client side." I completely agree and, in the end, this may be what I end up doing. The problem is that I have to accept calls from many pre-existing forms that do not use an 'include' or template structure, so it would be a lot of going back and changing field names (and some corresponding JS) manually. How can I access the raw JSON? I have been trying but didn't want to clutter the question.Pointing
@d12: See edit. I probably wouldn't make this choice, but if you do, capserOne's answer might help with some of the finer details once you're trying to figure out just what to do with raw JSON.Escarp
E
3

You can try and add another DataMember attribute with the lowercase name, but I assume you want a case-insensitive approach to reconciling member names, in which case, using extra DataMember attributes becomes unreasonable.

You could provide a IDataContractSurrogate implementation, but that is going to involve a lot of extra work on your part, as well as a lot of reflection (there's no way to do this in a static way which can be verified at compile-time).

Personally, I've given up on using the DataContractSerializer class when it comes to JSON. Rather, I use Json.NET for all of my JSON needs. I can see where you might be able to plug in Json.NET into the DataContractSerializer through a IDataContractSurrogate, but it's still going to be a little rough.

Json.NET was an easy choice given the difficulty of using the DataContractSerializer. Also, add to the fact that the DataContractSerializer for JSON doesn't handle DateTimeOffset values correctly, and it was a no-brainer for me, especially since I was working in an ASP.NET MVC environment (which allows me to shape the result any way I want).

This is really the better choice if you are exposing RESTful services using JSON as the encoding, you can mix and match that with WCF to expose all the endpoints over all the transport and message protocols you need.

Ethaethan answered 19/2, 2010 at 16:49 Comment(4)
This is interesting, have you somehow combined Json.NET with WCF or are you using a custom HTTP handler?Escarp
I would like to use one of the libraries to manually deserialize, but I'm not sure how to override the default deserialization that happens when the service receives the call. This probably dovetails with my above comment/question on how to access the raw JSON.Pointing
@Aaronaught: You can do it both ways. DataContractJsonSerializer derives from XmlObjectSerializer, and you can do the same, just injecting Json.NET in for their logic (note, this is not trivial). If you are using ASP.NET MVC for a RESTful interface, then all you do is create a custom ActionResult which takes an object, and in the ExecuteResult override, you use Json.NET to write JSON back to the output stream.Ethaethan
@d12: This question on the MSDN forums tells you how you can inject your own serializer as the default: social.msdn.microsoft.com/forums/en-US/wcf/thread/…Ethaethan
P
3

In order to achieve my goal of having a service that can accept a completely arbitrary list of "key": "value" pairs as a raw JSON string and then decide what do do with them without having to know the "key" names beforehand, I combined casper and aaron's advice.

1st, to access the raw JSON string, this MSDN blog was very helpful.

I was unable to simply change the single method parameter to String and the BodyStyle to WebMessageBodyStyle.Bare without problems. When setting BodyStyle to Bare, make sure that the endpoint behaviorConfiguration is set to <webHttp/> and not <enableWebScript/>.

The second note is that, as casperOne mentioned, the method can only have 1 parameter. This parameter needs to be a Stream in order to access the raw text though (see MSDN blog above).

Once you have the raw JSON string, it's just a matter of deserializing it into a StringDictionary. I chose JSON.Net for this and it works wonderfully. Here's a bare bones example to illustrate.

[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare,
    ResponseFormat = WebMessageFormat.Json)]
public string Register(Stream rawJSON)
{ 
    // Convert our raw JSON string into a key, value
    StreamReader sr = new StreamReader(rawJSON);
    Dictionary<string, string> registration =     
        JsonConvert.DeserializeObject<Dictionary<string, string>>(
            sr.ReadToEnd());

    // Loop through the fields we've been sent.
    foreach (KeyValuePair<string, string> pair in registration)
    {
        switch (pair.Key.ToLower())
        {
            case "firstname":
                return pair.Value;
                break;
        }

    }
}

This allows me to accept an arbitrary list of fields via JSON and for the field names to be case insensitive. I know it's not the strictest approach to data integrity or to how WCF services would ideally be structured, but, as far as I can tell, it's the simplest way to get where I want to go.

Pointing answered 24/2, 2010 at 22:43 Comment(0)
B
0

It really depends on what you are using the data for. In theory, you can keep this generic by having a string return type. However the data would represent an object and the client side control should know how to enumerate the JSON Object returned. You can return type which may also help.

Then in your client side code you can have a feature that knows how to determine and read different types.

Breakwater answered 19/2, 2010 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.