Skip to main content

jsonAction.org

jsonAction overview

The jsonAction protocol is a simple, lightweight RPC protocol that uses the request-response pattern to request a server to execute an action and return the result. jsonAction can be used for stateless or stateful communications as well as for running synchronous or asynchronous commands.

jsonAction is designed to be adopted easily. It uses the MIT license. It is simple and expandable. Like JSON Schema, jsonAction uses an open-world model, which defines a few required properties, a few optional properties, and allows an API to add additional properties to meet its specific needs.

jsonAction encapsulates all information about an action in one JSON document and all information about the result of the action in another JSON document.

Advantages of encapsulated actions:
  • Encapsulation allows an action to work across any protocol, including HTTP, WebSocket, MQTT, AMQP, and so forth.

  • Encapsulation also allows actions to be stored in any system and replayed as desired.

    For example, actions can be stored in a file system and replayed on demand. They can also be stored in a JSON database where queries and searches can find specific actions to be executed and they can be sent to message queue brokers to asynchronously deliver commands to subscribers.

These examples illustrate the core features of jsonAction.

Example 1. Minimal request and response

Request

The JSON requests the server to perform an action called "doSomething".

{
  "action": "doSomething"
}

Response

The response contains the "errorCode" property, which is set to zero for a successfully completed action. The  "errorCode" property is set to a non-zero number for a failed action.

{
  "errorCode": 0
}


Example 2. Request and response with parameters and results

Request

The JSON requests a server to do something with a parameter named "parameter1" set to "hello" .

{
  "action": "doSomething",
  "params":
  {
    "parameter1": "hello"
  }
}

Response

The response contains a successful result containing a "result1" property set to "world".

{
  "result":
  {
    "result1": "world"
  },
  "errorCode": 0
}


Example 3. Asynchronous request and response

Request

A request may have a "requestId" property that may contain any valid JSON value, such as a JSON object or a string.

{  
  "requestId": { "any": "value" },  
  "action": "doSomething"
}

Response

If "requestId" is present in the request, the server returns it without modification. This helps a client correlate an asynchronous response with the original request.

{  
  "requestId": { "any": "value" },
  "errorCode": 0
}


Example 4. Full-featured request

A request may contain the following standard properties. It may also include other properties defined by an API.

{
  "requestId": "uniqueValueSuppliedByClient",
  "authToken": "uniqueAuthorizationTokenFromTheServer",

  "api": "someAPI",
  "apiVersion": "1.0",
  "action": "doSomething",
  "params": {},

  "responseOptions": {
    "binaryFormat": "hex",
    "numberFormat": "string"
  },
  "debug": "max"
}
Optional request properties:
  • "authToken" authorizes a request.

  • "api" tells the server what API it should use to run the action. It is a namespace for an action.

  • "apiVersion" tells the server to use a specific version of the API. When omitted, the server uses the latest version.

  • "responseOptions" tells the server how to format a response.

    • "binaryFormat" tells the server how to format binary data embedded in strings.

    • "numberFormat" tells the server how to format numeric data.

  • "debug" tells the server what level of debugging information to return in the "debugInfo" property. When omitted, the server omits "debugInfo" in the response.



Example 5. Full-featured response

A response may contain the following standard properties along with additional properties supported by an API.

{
  "requestId": "uniqueValueSuppliedByClient",
  "authToken": "uniqueAuthorizationTokenFromTheServer",

  "result": {},

  "errorCode": 0,
  "errorMessage": "",

  "debugInfo":
  {
    "request": {},
    "serverSuppliedValues": {},
    "errorData": {},
    "warnings": [ 
      { 
        "warningCode": 0, 
        "warningMessage": "", 
        "warningData": {} 
      } 
    ]
  }
}
Optional response properties:
  • "errorMessage" contains a string describing an error.

  • "debugInfo" contains troubleshooting information. It is present when the "debug" property in the request is set to "max", or another debugging level accepted by the API. It may contain the following properties to help developers and applications troubleshoot the API.

    • "request" contains the original request received by the server.

    • "serverSuppliedValues" contains values for properties that the server supplies when optional parameters are omitted in the request.

    • "errorData" potentially contains additional information about an error.

    • "warnings" potentially contains warning codes, messages, and data from the server when a successful action needs to communicate additional concerns. For example, a query may include a warning that it found no records even though it ran without errors.



Applications need an effective way for computers to exchange information and execute remote actions. Implementing a message exchange is expensive. It must stand the test of time to get a maximal return on the investment.

To achieve these business objectives, the message exchange must achieve the following goals:
  • Reduce implementation and maintenance costs.

  • Evolve to meet changing requirements.

  • Work over any network protocol in existence today and in the future.

To meet these goals, each exchanged message must be:
  • Formatted simply

  • Structured simply

  • Self-describing

  • Extensible

  • Versioned

  • Self-contained

  • Usable in all major programming languages

  • JSON is the simplest, human-readable format for exchanging information between applications.

  • JSON is capable of representing any information.

  • JSON is the most natural fit for most programming languages. 

  • All information can be exchanged by sending one JSON document and returning one JSON document.

  • JSON can work over any protocol.

Why jsonAction instead of REST?:
  • REST is dependent on one protocol, HTTP. REST complicates software development because it spreads request information across multiple parts of HTTP and does not define adequate standards for structuring requests and responses.

    • The URL defines the resource the API should process. REST does not define a standard for how an API should interpret the structure of a URL. Developers use a variety of techniques for identifying resources.

    • A query string in the URL can be used to modify a request. REST does not define a standard for the structure of a query string and how the API should use it.

    • The HTTP verb specifies the action that an API should perform. The five HTTP verbs (GET, PUT, PATCH, DELETE, and POST) cannot adequately describe the actions an API needs to perform. In addition, GET and PUT control caching in reverse proxy caches. Because data is highly connected, APIs often need more control of caching to ensure data integrity. Thus, most REST APIs primarily use POST, PATCH, and DELETE.

    • HTTP headers contain additional information that an API can use to modify API behavior. REST does not define a standard for what information goes in cookies and how this information should be used by an API. REST also does not define a standard for authentication. There are a wide variety of complicated standards for authentication and authorization that put information in the header that an API must process correctly.

    • The HTTP body optionally contains a payload that communicates information to the server. REST does not define a standard for the content of the payload and it does not define a data structure for the payload.

  • jsonAction, simplifies and standardizes API communications.

    • It works over any protocol.

    • It puts all information in a JSON document. This makes it easy for applications to create, parse, and use the information. It also makes it easy to log message exchanges and to replay them later. It makes it easy to share messages over any medium. It is easy to store messages in a database, run queries to retrieve subsets of messages, replace properties, and send actions to APIs on demand or on a schedule.

    • It standardizes the core structure of the JSON requests and responses to make it easy to process actions, pass parameters, return responses, control versioning, identify errors, and troubleshoot issues.

    • It gives an API the flexibility it needs to define any type and amount of information it needs in requests and responses.

jsonAction APIs must maintain backward capability.

Three approaches to prevent a change from breaking a jsonAction API:
  • Only make non-breaking changes.

  • Simultaneously support old and new features in the current version of the API.

  • Create a new version of the API and simultaneously support both API versions.

Non-breaking changes

Non-breaking changes:
  • Adding an optional property

  • Adding a new enumerated value

  • Increasing the length of a property value

  • Increasing the min and max range of a property value

Breaking changes

Breaking changes that always require a new version of the API:
  • Adding a new required property

  • Turning an optional property into a required property

  • Prohibiting a property (removing a property and returning an error when the property is in a request)

Breaking changes that can be mitigated by simultaneously supporting old and new features in the same API version:
  • Changing observable behavior

    This becomes a non-breaking change if you add a new optional property that causes the new behavior to occur and the omission of the property causes the old behavior to occur.

  • Renaming a property

    This becomes a non-breaking change if you enhance the API to simultaneously support the old and new property names.

  • Replacing an optional property

    This becomes a non-breaking change when you create a new optional property that can be used in place of the old property. The old property can continue to be used. The new property can be used in place of the old property. If both the old and new properties are present, the API returns an error — for example, changing a single value into an array of values is a common example that typically causes a property to be renamed to a plural name. When the user wants to use the single value, they can use the singular property name. Conversely, when the user wants to use an array of values, they can use the plural property name.

  • Changing the JSON data type of a property

    This becomes a non-breaking change when the API simultaneously supports both the old and new data types — for example, when the old property definition supports a boolean value and the new property definition supports a string enum value, the API recognizes the difference in the JSON type and processes the request accordingly.

  • Changing the type of data embedded in the value of a JSON string

    You may change a date embedded in a JSON string to a floating point number. This becomes a non-breaking change when the API simultaneously supports both the old and new data types. This requires the API to recognize the data type embedded in the string.

  • Reorganizing the property hierarchy

    This is a major change that is hard for customers and APIs to implement. This becomes a non-breaking change when the API simultaneously supports both the old and new property hierarchies. This change is particularly hard to document and for customers to adopt.

  • Decreasing the length of a property value

    This becomes a non-breaking change when the API simultaneously supports both the old and new property lengths.

  • Decreasing the min and max range of a property value

    This becomes a non-breaking change when the API simultaneously supports both the old and new property ranges.

Best practices for future-proofing an API

  • Do not abbreviate property names and make property names self-explanatory.

  • Prefer a string enum over a boolean value because new enumerated values can be added in the future.

  • Minimize the number of required properties.

  • If there is a possibility of a property needing multiple values, define it as an array and give the property a plural name.

  • Keep the JSON structure as flat as possible since nested structures are more likely to create breaking changes.

Copyright 2023, FairCom USA CORPORATION

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.