Json vs JSON.NET vs JsonValue

Topics: jQuery, Web Api
Jun 10, 2011 at 5:23 AM

Hey guys,

Probably something that's well known, or previously talked about but I've missed and would appreciate the pointer.

I've seen quite a few samples of people implementing a Media Formatter for JSON.NET and I was wondering why you would use that instead of the inbuilt serializer/deserializer.

I was also wondering why you would hand code the JsonValue instead.

I'm planning on trying to work with a site that will use jQuery, implementing an APIKey (which I've seen samples around here which are fantastic), and hopefully some SSL to protect it (although how much protection you get when you have to have the API key in the javascript source to the site?).

I'm also hoping to use the same endpoint and keys to allow certain web and Windows client apps to query, probably using Newtonsoft's Json.NET for the win clients.

Are there particular gotchas?  I've heard that the javascript date format is different to what Windows serves up, so does the inbuilt Json formatter take care of that? JSON.NET? Any other way?

Is there anything else to bite me when working with Json?

I'm just basically looking for a point in the right direction and which Json is the right Json to start using?

Thanks,

Andrew

Coordinator
Jun 10, 2011 at 5:31 AM

One big benefit of JSON.NET is it's blazingly fast. It also has better support for JSON itself than our JSON Serializer for example it's date handling (which you mentioned) is funky. JsonValue greatly improves our JSON serialization story and also offers a more true to JSON experience, but it only works if the service is accept a param of type JsonValue wheras JSON.NET supports CLR types. Thus if you use JSON.NET your service can take your own custom object which is desirable if you want to support custom formats that are not JSON.

Using JsonValue removes the need for having a static type. It is desirable for scenarios where your service is only being consumed by JSON clients like jQuery / extjs or non-browser clients.

Beyond that I would say that allowing to use something llike JSON.NET is all about choice. It gives you the flexibility to benefit from all the great work of the OSS community if that is what you want to do.

In terms of API Key, it should be trivial to implement through authoring a custom handler. Pablo Cibraro created a nice sample on API Keys which I have made available on SkyDrive here

Hope this helps.

Glenn

 

 

Jun 10, 2011 at 10:28 AM

Tobin; in addition to what Glenn Block said, we've added really neat features using JSON.NET. Particularly on the side of custom (de)serialization. (Note; this might also be achievable using other ways but with JSON.NET I can guarantee it because we have it in place already).

So here goes, we wanted to:

- Make the Accept http type responsible for the type of returned data (naturally, because this fits in the whole WebAPI Formatter experience).

- Support Xml and JSON as return type.

- Put a metadata header on top of each returned message (that's a big one).

- Read a metadata header from the top of each returned message (that's a big one too).

- Make each returnable object reposponsible for it's own (de)serialization (this includes the header). This is a pattern thing I guess.

Implementation:

Take these methods for example (they are actual methods from our architecture):

        HttpResponseMessage<EmployeeProxy> GetEmployee(string subject, string customer, string employee);

        HttpResponseMessage SetEmployeeData(string subject, string customer, string employee, EmployeeProxy employeeData);

It's in the wcf contract and GetEmployee needn't know anything about how EmployeeProxy is serialized. Also, SetEmployeeData doesn't need to know how EmployeeProxy is deserialized. Basically, with setting the Content and Accept type you can sent data in xml and get it back as json (if you would want to, different discussion).

All the proxy objects MUST implement the following interface:

 

    public interface IADPSerializable
    {
        void WriteJSON(JsonWriter writer);
        void WriteJSON(Stream stream); 
        void ReadJSON(string json);
        void ReadJSON(Stream json);
        
        void WriteXml(XmlWriter writer);
        void WriteXml(Stream stream);
        void ReadXml(string xml);
        void ReadXml(Stream xml);
    }

 

Note that JsonWriter is a JSON.NET type (!)

Some heavy lifting is done by the ADPSerializableBase (which implements the interface) and each Proxy object ends up inheriting from it. This Proxy object must implement these abstract methods:

 

        public abstract void WriteJSON(JsonWriter writer);

        public abstract void ReadJSON(string json);

        public abstract void WriteXml(XmlWriter writer);

        public abstract void ReadXml(string xml);

 

What you're saying here it that:

- An object implementating these abstract methods know how to (de)serialize itself to/from xml.

- An object implementating these abstract methods know how to (de)serialize itself to/from json.

This is what our JSONFormatter looks like (xml formatter nearly identical):

 

    public class ADPJSONFormatter : MediaTypeFormatter
    {
        public ADPJSONFormatter()
        {
            this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
            this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
        }

        public override object OnReadFromStream(Type type, Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders)
        {
            ADPMessageWrap gjw = new ADPMessageWrap(stream, SupportedContentType.JSON);
            return gjw.InnerObject;
        }

        public override void OnWriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, System.Net.TransportContext context)
        {
            ADPMessageWrap gjw = new ADPMessageWrap(value);
            gjw.WriteJSON(stream);
        }
    }

The ADPMessageWrap (which also inherites from ADPSerializableBase ands represents our message header) also 'knows' how to (de)serialize itself and delegates the stream to it's inner/child object (i.e. the EmployeeProxy) to let is serialize itself.
This is where JSON.NET really shines, watch how cool (de)serialization is getting:
        public override void WriteJSON(JsonWriter writer)
        {
            writer.WriteStartObject(); // {

            writer.WritePropertyName("adpmessage");

            writer.WriteStartObject(); // {
            
            writer.WritePropertyName("app");
            writer.WriteValue(Application);

            writer.WritePropertyName("domainobjecttype");
            writer.WriteValue(InnerObjectType.FullName.ToLower());

            writer.WritePropertyName("domainobject");

            if (InnerObject is IADPSerializable)
            {
                ((IADPSerializable)InnerObject).WriteJSON(writer);
            }
            else
            {
                writer.WriteValue(InnerObject.ToString());
            }

            writer.WriteEndObject(); // }
            writer.WriteEndObject(); // }        
        }

        public override void ReadJSON(string json)
        {
            // Always throw exception if the value is not found.
            JObject jo = JObject.Parse(json);

            this.Application = jo.SelectToken("adpmessage.app", true).ToString();

            string typeName = jo.SelectToken("adpmessage.domainobjecttype", true).ToString();

            string innerObjectValue = jo.SelectToken("adpmessage.domainobject", true).ToString();
            InnerObjectType = Type.GetType(typeName, true, true);

            InnerObject = Activator.CreateInstance(InnerObjectType);

            if (InnerObject is IADPSerializable)
            {
                IADPSerializable ijson = ((IADPSerializable)InnerObject);
                ijson.ReadJSON(innerObjectValue);
            }
            else
            {
                InnerObject = Convert.ChangeType(innerObjectValue, InnerObjectType);
            }
        }

        public override void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("adpmessage");

            writer.WriteElementString("app", Application);
            writer.WriteElementString("domainobjecttype", InnerObjectType.FullName.ToLower());

            writer.WriteStartElement("domainobject");

            if (InnerObject is IADPSerializable)
            {
                ((IADPSerializable)InnerObject).WriteXml(writer);
            }
            else
            {
                writer.WriteValue(InnerObject.ToString());
            }

            writer.WriteEndElement(); // domainobject
            writer.WriteEndElement(); // adpmessage
        }

        public override void ReadXml(string xml)
        {
            throw new NotImplementedException();
        }

And the EmployeeProxy object looks like this:

    public class EmployeeProxy : ADPSerializerBase
    {
        public EmployeeProxy()
        {
            Items = new List<EmployeeItemProxy>();
        }

        public List<EmployeeItemProxy> Items { get; private set; }

        public override void WriteJSON(JsonWriter writer)
        {

            writer.WriteStartObject(); // {

                writer.WritePropertyName("employee");
                writer.WriteStartObject(); // {

                    writer.WritePropertyName("items");
                        
                    writer.WriteStartObject(); // {

                    foreach (EmployeeItemProxy item in Items)
                    {
                        writer.WritePropertyName(item.Column);
                        if (item.Value == null)
                        {
                            writer.WriteValue("");
                        }
                        else
                        {
                            if (item.Value is DateTime)
                            {
                                //new JavaScriptDateTimeConverter().
                                DateTime dt = (DateTime)item.Value;
                                TimeSpan ts = dt - new DateTime(1970, 1, 1);
                                writer.WriteRawValue("new Date(" + ts.TotalMilliseconds + ")");
                            }
                            else
                            {
                                writer.WriteValue(item.Value.ToString());
                            }
                            
                        }
                        
                    }
                        
                    writer.WriteEndObject(); // }
                writer.WriteEndObject(); // }
            writer.WriteEndObject(); // }
        }

        public override void ReadJSON(string json)
        {
            // Always throw exception if the value is not found.
            JObject jo = JObject.Parse(json);

            JToken jt = jo.SelectToken("employee.items", true);

            Dictionary<string, string> values = JsonConvert.DeserializeObject<Dictionary<string, string>>(jt.ToString());
            
            foreach (KeyValuePair<string, string> item in values)
	        {
                Items.Add(new EmployeeItemProxy(item.Key, item.Value));
	        }
        }

        public override void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("employee");

            writer.WriteStartElement("items");

            foreach (EmployeeItemProxy item in Items)
            {
                writer.WriteElementString(item.Column, item.Value == null ? "[null]" : item.Value.ToString());
            }

            writer.WriteEndElement(); // items

            writer.WriteEndElement(); // employee
        }

        public override void ReadXml(string xml)
        {
            throw new NotImplementedException();
        }

Sorry for maybe the overly long example, the idea I'm trying to get across is that JSON.NET allows to make (de)serialization on the same abstract level as the xml stuff.

Note that this example is only really valid if you need 100% control over the JSON/XML format that goes over the line. Which we do, but this may not aply to your problem.

Gerben.