This page explains how to use ArduinoJson with PubSubClient, a popular MQTT library for Arduino. It shows how to use the JSON format in MQTT messages, but you can easily adapt the examples to use MessagePack instead.

Deserializing a JSON document in MQTT message

Once your program has subscribed to an MQTT topic, you can call deserializeJson() from the callback function.

void callback(char* topic, byte* payload, unsigned int length) {
  JsonDocument doc;
  deserializeJson(doc, payload, length);
  // use the JsonDocument as usual...
}

Avoiding dangling pointers

Even if you don’t use a global JsonDocument, the callback is a great place to create dangling pointers. Make sure you never store a pointer that you acquired in the callback:

// BAD EXAMPLE: DON'T DO THAT!!!
const char *name = NULL;

void callback(char* topic, byte* payload, unsigned int length) {
  JsonDocument doc;
  deserializeJson(doc, payload, length);
  name = doc["name"]; // 💀 pointer is invalid as soon as function returns
}

Instead, make a copy of the string:

const char name[32] = "";

void callback(char* topic, byte* payload, unsigned int length) {
  JsonDocument doc;
  deserializeJson(doc, payload, length);
  strlcpy(name, doc["name"] | "default", sizeof(name));
}

Of course, you can use a String if you prefer.

Serializing a JSON document into an MQTT message

To publish a JSON document to an MQTT topic, you need to serialize it to a temporary buffer:

char buffer[256];
serializeJson(doc, buffer);
client.publish("outTopic", buffer);

You can save a few CPU cycles by passing the size of the payload to publish():

char buffer[256];
size_t n = serializeJson(doc, buffer);
client.publish("outTopic", buffer, n);

By default, PubSubClient limits the message size to 256 bytes (including header); see the documentation.

Can we avoid the temporary buffer?

It’s tempting to remove the temporary buffer to save some memory. For example, we can write:

client.beginPublish(topic, measureJson(doc), retained);
serializeJson(doc, client);
client.endPublish();

However, this code is much slower than the one with a temporary buffer (100 to 200 times slower from our experience).

This slowness is due to the Client class that sends bytes one by one. To improve the speed, we need to insert a small buffer as shown in How to improve (de)serialization speed?:

client.beginPublish(topic, measureJson(doc), retained);
BufferingPrint bufferedClient(client, 32);
serializeJson(doc, bufferedClient);
bufferedClient.flush();
client.endPublish();

Notice that I used a BufferingPrint instead of WriteBufferingClient because, despite its name, the PubSubClient class doesn’t implement the Client but the Print interface. For more information on BufferingPrint, see the README of the StreamUtils library.

This works, but as you can see, it’s much more complicated than the original code, so I don’t think it’s worth the effort.

See also

Global warming stripes by Professor Ed Hawkins (University of Reading)