We are all grateful for and love the native JSON support that was introduced to the platform in Winter 12 it has certainly made lives much simpler when it comes to integrating with external systems.
One of the reasons that the introduction of JSON support was such a step change for the platform was the prevalence of JSON in the wild as a way of communicating – it has over recent history become the de facto method for data transport replacing the much bulky and often more complex XML. One of the biggest reasons in my eyes for the rapid adoption of JSON is it’s flexibility, allowing interfaces to be changed quickly and easily. This of course, whilst an advantage for JSON isn’t a great selling point for systems architecture in general – historically interfaces are expected to be well defined and slow moving: JSON bucks this trend massively.
Accepting that changes to interfaces have become rapid and often unpredictable we see that JSON parsers are being written to cope with these changes. In general they are very flexible; not caring if fields are added or removed and instead just doing the best they can with what they’ve got. That is of course unless you happen to be developing on the Force.com platform.
If we take the following, very simple, JSON:
Using the built in deserializer to deserialize this into the follow APEX class works as expected:
All good. Now what happens if we add a new field to the APEX class but leave the JSON as it is, without the new field?
Deserializing the JSON into this class works and as expected fieldb is null in the resulting object. Brilliant , this is exactly the flexibility that we would expect. So what happens when we have the opposite situation; a field in the JSON that isn’t a field on the class? Keeping the class the same as above but changing the JSON to look like this:
Trying to deserialize this JSON into the Example class will now throw an exception.
FATAL_ERROR System.JSONException: Unknown field: Example.fieldc
This is not so brilliant, in fact out of the two situations I’ve described above if the platform was going to thrown an exception at all I’d rather it be in the first instance – after all I was clearly expecting fieldb so it would be nice to know I didn’t receive it in my JSON. However going back to the principal of flexibility in JSON and it’s parsers I think it would be best to not throw an exception in either case.
So, why do I think it’s such bad form to throw an exception in the second case? The problem is that you become extremely tightly coupled to the interfaces that you’re integrating with, something that I don’t really see as being in the spirit of JSON. Why are you so tightly coupled? Well every time your third party adds a field to their JSON return all of your deserialization is going to fail and fail spectacularly too. Once you know about the change it’s quickly fixed, in the case above simply add fieldc to you Example class but I shouldn’t really be having to do this it makes the system so brittle. This is obviously amplified if you’re code is in a managed package and installed in other Orgs; suddenly a third party change makes your product fail andthat’s all the user sees, they don’t know and don’t care that it’s not your fault.
I think this needs to be changed and will raise and idea to assit in this process but I would be interested in your views on this. Am I wrong in my thinking? Am I expecting too much from the platform here? Should there be exceptions in both cases?
Thanks to James Melville (@jamesmelv) for pointing me to the Summer 12 release notes:
JSON methods for deserializing JSON content now perform more lenient parsing by default. They don’t throw any errors if the JSON input string contains attributes, such as fields or objects, that don’t exist in the type of the deserialized output object. They simply ignore these extra attributes when deserializing the JSON string.
This serves me right for a) not posting these things when I think of them and for b) not reading release notes carefully enough.
If this turns out to be true then I gracefully rescind my earlier rant.