JSON Protocol Versioning
JSON is one of the most widely used data formats in the world. JSON is used to define information exchanged between computers, describe data file formats, enable various web services, and so on. In fact, JSON is so pervasive in the technology industry now that virtually every single development platform offers computer programmers a multiplicity of ways in which the parse, access, and create JSON data structures.
The use of JSON is real-time multimedia communication systems was quick to gain momentum, unlike its XML alternative. However, one issue often ignored is a proper approach to performing protocol versioning. Nonetheless, it is important to understand how JSON should be used to ensure proper interoperability between systems, including new versions of software released from the same manufacturer. In many cases, we have observed JSON being used rather rigidly and in such a way as to risk having older systems unable to properly communicate with newer systems and vise versa. The reason for such design approaches is, we believe, due to the nature of the "always on" Internet and the web services versioning models widely in use.
JSON protocol versioning is not a topic limited to real-time communication systems, however. Rather, this is a topic of importance to any two communicating systems that might utilize JSON as the protocol signaling syntax and which may be using different versions of the protocol at any given time.
Let us first show you an example of the kinds of problems one can encounter when using JSON and not employing proper versioning techniques. This example is a rather gross one, but it still represents a real-world approach we have seen with traditional web services. Consider this message as an initial message of version 1 of a given protocol.
{
"user": "paulej",
"id": 1000
}
Now, let us suppose that the protocol designer changed the syntax of the protocol for version 2, so that the initial message transmitted by the initiating device was the following:
{
"user": {
"name": "paulej",
"id": 1000
}
}
Clearly, we would not expect the version 1 device to understand what "user" means. In the world of traditional web services, this did not matter since all web servers support all versions and a specific version is requested explicitly, generally through the URI. But, it is significant for communicating systems that do not necessarily support all versions simultaneously.
With legacy signaling systems, great care was taken to ensure proper forward and backward compatibility between systems. Such design principles are no less important today and should be applied appropriately to JSON to ensure that two communicating JSON-based systems can properly interwork when protocol enhancements are inevitably made.
Ensuring proper JSON protocol interworking requires protocol designers to follow a basic set of rules, borrowing from experience, but applied to the JSON-based syntax.
Signal Version Information
As hard as software engineers strive to ensure that products are interoperable between versions, there is always a chance that an error is made and backward compatibility is broken. To address such issues, version information, including the protocol version number, product identifier, and manufacturer identification, should be transmitted in any initial message sent between any two communicating devices. In most cases, this information should be ignored. But, it has been necessary in the past to insert logic into software to interwork with specific releases from certain manufacturers who did something that wasn't quite right.
As such, any JSON-based protocol should have manufacturer and product or protocol version information similar to the following.
{
"version": {
"manufacturer": "Packetizer",
"version": 1.1
}
}
In this example, the "version" object indicates contains members that identify the manufacturer and the protocol version. The manufacturer identifier and version number should be consistent in form and style from one release to another so that it might be possible to define rules to handle special situations when a given product older than a given release, for example, needs to be treated differently than newer products of the same type.
Ideally, every single message would contain this information, though it does add to the protocol overhead. At the very least, initial signaling messages should contain this information.
Lastly, we cannot stress enough that properly versioned software should not rely on the version numbers to operate properly. If properly designed following the rules described on this page, communication software should be fully interoperable without having any checks performed on the version, vendor, and product information attributes.
URIs Should be Void of Version Numbers
When building a communication system that relies on URIs, one should never place version numbers into the URIs, as is often done with traditional web services. The reason is that if one device tries to access https://example.org/v4/some_service and it does not exist, we have wasted time and resources. Following the guidelines presented on this page, it should be possible to access a service simply through the path https://example.org/some_service.
While this guidance is intended for communication systems that are not traditional web server-based systems, this same guidance is still applicable to such traditional web services. After all, if you design a web service and might offer 15 versions of the software over time, would you want to maintain 15 different URLs with slightly different software? Software development practices like that lead to horribly bloated systems. When or if it is necessary to observe a version number, only then would your software need to follow a slightly different code path.
Mandatory and Optional Elements and Attributes
One of the most important rules we can offer is that all elements and attributes within a signaling system should be optional and, where a required element or attribute is missing, a default value should be specified.
In the first version of a protocol specification, some elements or attributes may be defined as mandatory. However, one cannot create new mandatory elements or attributes in a second revision of a protocol. After all, the version 1 devices could not have known about new elements or attributes.
Practically speaking, this can be a challenge. Even so, this rule is vitally important.Consider this example:
{
"version": {
"manufacturer": "Packetizer",
"version": 1.0
},
"type": "initiate",
"destination": {
"tel": "+15551212"
}
}
Suppose the above example is a protocol that initiates a voice telephone call. What information is absolutely required to be present? At the very least, one would need to define the destination address of whom the caller is trying to reach. Is the caller's identity needed? Not really. In fact, virtually everything else could be treated as optional. If the caller's identity is missing and you wish to display something on the screen to indicate who is calling, a reasonable default might be "Anonymous".
Now, that is not to say that the call will not fail if the caller's identity is not present. However, that would be a matter of policy. The JSON parser should properly process the message and a decision on how to handle the message should be made at the application level. Even then, a decision to fail the call or to take other such drastic action should be avoided.
When designing an JSON-based communication protocol, think very carefully about what information is and is not absolutely necessary in the first version. When creating subsequent versions, it is incumbent on the protocol designer to define default values where necessary for elements and attributes that are needed, but are missing in the signaling. Older devices are definitely not going to be able to send them.
A Second Version of the Protocol
In a second (or additional revisions of the protocol) should be able to introduce any number of additional signaling elements without breaking interoperability with 1.0 systems. For example, this might be a valid version 1.1 protocol message.
{
"version": {
"manufacturer": "Packetizer",
"version": 1.1
},
"type": "initiate",
"destination": {
"tel": "+14085551212"
},
"callerInfo": {
"name": "John Doe",
"email": "john@example.com"
"tel": "+14085557414"
}
}