Using JSON in C/C++

by Sergey Lyubka, Cesanta

@CesantaHQ

What is JSON


  {
      "name": "sensor_1",
      "wifi": {
          "enable": true,
          "ssid": "MyWifiNetwork",
          "password": "ы"
      },
      "location": {
          "lat": 53.3413712,
          "lon": -6.2396965
      }
  }
  • Simple human-readable format
  • Keys can go in any order
  • Strings are utf8. Newlines, non-printable chars are escaped.

What it is used for?

Most common use cases

  • Storing configuration
    • Easy to keep structure in a configuration, keeping it human-readable at the same time
    • For example, Sublime Text editor stores its configuration in JSON
  • Exchanging data between systems
    • Communicating systems can be written using different technologies, e.g. backend in C++ and client application in JavaScript
    • For example, web applications like Gmail use JSON to exchange data with browser

Using JSON in C/C++

  • There is a C/C++ data structure
  • This structure needs to be converted to/from JSON

{
    "name": "sensor_1",
    "wifi": {
        "enable": true,
        "ssid": "MyWifiNetwork",
        "password": "ы"
    },
    "location": {
        "lat": 53.3413712,
        "lon": -6.2396965
    }
}

/* Configuration structure */
struct config {
  char *name;
  struct {
    int enable;
    char *ssid;
    char *pass;
  } wifi;
  struct {
    double lat;
    double lon;
  } location;
};

Method 1: in-memory tree

  • C/C++ do not have introspection capabilities (assuming no RTTI support)
  • Therefore C/C++ JSON libraries build an intermediate representation of the JSON object in memory
  • JSON libraries are listed on http://json.org

Intermediate tree API



  /* Parsing JSON -> C/C++ structure */
  struct config c;
  struct json_obj = parse(json_string);

  c.name = json_obj.attr("name").to_string();
  c.location.lat = json_obj.attr("location").attr("lat").to_double();
  ...  


  /* Generating C/C++ structure -> JSON */
  struct config c;
  struct json_obj = init_json_obj();

  json_obj.add_string("name", "my_device");
  struct json_obj l = json_obj.add_emty_obj("location");
  l.add_double("lat", 1.2345);
  l.add_double("lon", -4,5678);
  ...

  cout << json_obj.to_string();

Method 2: code generation

  • The idea is: take C/C++ structure definition from a header file
  • Run some external tool that parses C/C++ source file and creates code to convert in-memory C/C++ object into a JSON string and vice versa
  • Pro:
    • Handy C/C++ API for JSON marshalling
    • No human error factor
  • Contra:
    • It could be painful to integrate an external tool into a build process - generated files, more complex build, a tool can drag dependencies, etc
    • What if some structure fields need to be omitted?

Code generation example


  /* file config.h : configuration structure */
  struct config {
    char *name;
    double latitude;
  };

  static struct mem_layout {
    const char *name;
    size_t offset;
    int type;
  } config_mem_layout = {
    {"name", offsetof(struct config, name), TYPE_STRING},
    {"latitude", offsetof(struct config, latitude), TYPE_NUMBER}
  };

  char *config_to_json(const struct config *cfg) {
    /* Having a pointer and memory layout, we can serialize */
    ...
  }

Running a tool gives us marshalling API:


  /* file config_json.h : marshalling API */
  char *config_to_json(const struct config *);

Method 3: scanf/printf

  • What if we want fetch values from JSON string directly into C/C++ variables?
  • No intermediate tree?
  • No code generation?
  • Just like scanf/printf does?

Method 3: issues

  • JSON keys can come in any order - scanf does not work
  • JSON string values can be escaped
  • It is hard to write printf/scanf for deeply nested structures
  • But fear not. There is a library that can solve all of that.

Method 3: parsing


  // str has the following JSON string (notice keys are out of order):
  // { "a": 123, "c": true, "b": "hi" }

  int a, b;
  char *c;
  json_scanf(str, strlen(str), "{ a:%d, b:%Q, c:%B }", &a, &b, &c);

  // a == 123, b == "hi", c == true
  • Extend scanf/printf with extra %B, %Q, %M
  • Auto-escape keys and string values

Method 3: printing


  json_printf(&out, "{%Q: %d, x: [%B, %B], y: %Q}", "foo", 123, 0, -1, "hi");
  // Result:
  // {"foo": 123, "x": [false, true], "y": "hi"}

Frozen JSON library

Thank You!

contact me at

sergey.lyubka@cesanta.com

Made with Slides.com