Does your realtime application need to call APIs that take JSON strings as input and/or return them as output? Does it need to persist data in a JSON database? Or do you want to serialize your application data in JSON-based log files? These are just a few examples of scenarios which involve JSON. In this newsletter we'll look at some features in Model RealTime which can help when you need JSON support in your realtime application.
Encoding is the process of serializing data from memory into a string representation. Types in your application which have a type descriptor can be encoded by calling the encode function of the type descriptor. It has the following prototype (where YourType
is the name of the type):
int rtg_YourType_encode(const RTObject_class* type, const YourType* source, RTEncoding* coding);
Here type
is the type descriptor object that is generated for YourType
, source
is the data to be serialized and coding
is an object that implements the actual encoding.
When the TargetRTS needs to encode a data object, for example when logging it or when using the Model Debugger, it uses RTAsciiEncoding which encodes the object into a compact ASCII format. However, the TargetRTS also provides a class RTJsonEncoding which you can use if you want to encode the data object into a JSON string instead.
Assume you have a class Request
in your model defined like this:
To encode an object of this class into JSON you could do like this:
Request req("001" /* id */, false /* background */, Priority::medium /* prio */);
RTDynamicStringOutBuffer buffer;
RTJsonEncoding coding(&buffer);
coding.put_struct(&req, &RTType_Request);
std::cout << buffer.getString() << std::endl;
This will print the following string to stdout
:
{"id" : "001","background" : false,"prio" : 1}
Note that neither RTDynamicStringOutBuffer.h
or RTJsonEncoding.h
are TargetRTS headers that are included by default.
While RTJsonEncoding
always will produce valid JSON, you may sometimes want it to produce a different JSON. For example, by default enum literals are encoded as integers (first literal is 0, second is 1 etc). That's why Priority::medium
is encoded as 1 in the example above. If you want to customize the encoded JSON you have two options:
Priority
literals as strings instead, you could implement an encode function for Priority
like this:if (*source == Priority::low)
return coding->put_string("low");
else if (*source == Priority::medium)
return coding->put_string("medium");
else if (*source == Priority::high)
return coding->put_string("high");
return coding->put_string("UNKNOWN");
With this in place, the JSON encoding for the above example would instead become:
{"id" : "001","background" : false,"prio" : "medium"}
Decoding is the opposite of encoding, i.e. to deserialize a string representation of data into memory. Decoding is performed by the decode function of the type descriptor which has the following prototype:
int rtg_YourType_decode(const RTObject_class* type, YourType* target, RTDecoding* coding);
Here type
is the type descriptor object that is generated for YourType
, target
is the data object to be populated and coding
is an object that implements the actual decoding.
The TargetRTS uses an ASCII decoder by default but also provides a JSON decoder. The general contract between an encoder and a decoder is that the decoder's get()
function should be able to read what the encoder's put()
function produces. This means that the JSON encoder's put()
function must produce a string that includes the type name, so that the JSON decoder's get()
function can create the correct type of object. For the above example (without the JSON customization) it will look like this:
{Request}{"id" : "001","background" : false,"prio" : 1}
Note that this string is not valid JSON due to the type name prefix, which is why we used put_struct()
instead in the above example.
Also note that if you have customized the JSON encoding then you also must customize the JSON decoding in a similar way (to fulfill the contract that the decoder can read the format written by the encoder). A decode function for Priority
that matches the custom encode function above could look like this:
char* c;
int r = coding->get_string(c);
if (r == 0)
return 0;
if (RTMemoryUtil::strcmp(c, "low") == 0)
*target = Priority::low;
else if (RTMemoryUtil::strcmp(c, "medium") == 0)
*target = Priority::medium;
else if (RTMemoryUtil::strcmp(c, "high") == 0)
*target = Priority::high;
delete[] c;
return r;
Sometimes you may need to parse JSON for a different reason than decoding it. For example, you may get JSON as the result of making an API call, and then need to parse the JSON to more easily extract the relevant information from it. There are several third-party C++ libraries for parsing JSON, but for convenience the TargetRTS also includes a general-purpose JSON parser implemented with the RTJsonParser class.
Read more about JSON parsing in the documentation.