Skip to content
Snippets Groups Projects
json.hpp 363 KiB
Newer Older
  static basic_json from_msgpack_internal(const std::vector<uint8_t> &v,
                                          size_t &idx)
  {
    // make sure reading 1 byte is safe
    check_length(v.size(), 1, idx);
    // store and increment index
    const size_t current_idx = idx++;
    if (v[current_idx] <= 0xbf)
      if (v[current_idx] <= 0x7f) // positive fixint
      {
        return v[current_idx];
      }
      if (v[current_idx] <= 0x8f) // fixmap
      {
        basic_json result = value_t::object;
        const size_t len = v[current_idx] & 0x0f;
        for (size_t i = 0; i < len; ++i)
        {
          std::string key = from_msgpack_internal(v, idx);
          result[key] = from_msgpack_internal(v, idx);
        }
        return result;
      }
      else if (v[current_idx] <= 0x9f) // fixarray
      {
        basic_json result = value_t::array;
        const size_t len = v[current_idx] & 0x0f;
        for (size_t i = 0; i < len; ++i)
        {
          result.push_back(from_msgpack_internal(v, idx));
        }
        return result;
      }
      else // fixstr
      {
        const size_t len = v[current_idx] & 0x1f;
        const size_t offset = current_idx + 1;
        idx += len; // skip content bytes
        check_length(v.size(), len, offset);
        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                           len);
      }
    else if (v[current_idx] >= 0xe0) // negative fixint
      return static_cast<int8_t>(v[current_idx]);
    else
    {
      switch (v[current_idx])
      {
        case 0xc0: // nil
        {
          return value_t::null;
        }
        case 0xc2: // false
        {
          return false;
        }
        case 0xc3: // true
        {
          return true;
        }
        case 0xca: // float 32
        {
          // copy bytes in reverse order into the double variable
          float res;
          for (size_t byte = 0; byte < sizeof(float); ++byte)
          {
            reinterpret_cast<uint8_t *>(&res)[sizeof(float) - byte - 1] =
                v.at(current_idx + 1 + byte);
          }
          idx += sizeof(float); // skip content bytes
          return res;
        }
        case 0xcb: // float 64
        {
          // copy bytes in reverse order into the double variable
          double res;
          for (size_t byte = 0; byte < sizeof(double); ++byte)
          {
            reinterpret_cast<uint8_t *>(&res)[sizeof(double) - byte - 1] =
                v.at(current_idx + 1 + byte);
          }
          idx += sizeof(double); // skip content bytes
          return res;
        }
        case 0xcc: // uint 8
        {
          idx += 1; // skip content byte
          return get_from_vector<uint8_t>(v, current_idx);
        }
        case 0xcd: // uint 16
        {
          idx += 2; // skip 2 content bytes
          return get_from_vector<uint16_t>(v, current_idx);
        }
        case 0xce: // uint 32
        {
          idx += 4; // skip 4 content bytes
          return get_from_vector<uint32_t>(v, current_idx);
        }
        case 0xcf: // uint 64
        {
          idx += 8; // skip 8 content bytes
          return get_from_vector<uint64_t>(v, current_idx);
        }
        case 0xd0: // int 8
        {
          idx += 1; // skip content byte
          return get_from_vector<int8_t>(v, current_idx);
        }
        case 0xd1: // int 16
        {
          idx += 2; // skip 2 content bytes
          return get_from_vector<int16_t>(v, current_idx);
        }
        case 0xd2: // int 32
        {
          idx += 4; // skip 4 content bytes
          return get_from_vector<int32_t>(v, current_idx);
        }
        case 0xd3: // int 64
        {
          idx += 8; // skip 8 content bytes
          return get_from_vector<int64_t>(v, current_idx);
        }
        case 0xd9: // str 8
        {
          const auto len =
              static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
          const size_t offset = current_idx + 2;
          idx += len + 1; // skip size byte + content bytes
          check_length(v.size(), len, offset);
          return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                             len);
        }
        case 0xda: // str 16
        {
          const auto len =
              static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
          const size_t offset = current_idx + 3;
          idx += len + 2; // skip 2 size bytes + content bytes
          check_length(v.size(), len, offset);
          return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                             len);
        }
        case 0xdb: // str 32
        {
          const auto len =
              static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
          const size_t offset = current_idx + 5;
          idx += len + 4; // skip 4 size bytes + content bytes
          check_length(v.size(), len, offset);
          return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                             len);
        }
        case 0xdc: // array 16
        {
          basic_json result = value_t::array;
          const auto len =
              static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
          idx += 2; // skip 2 size bytes
          for (size_t i = 0; i < len; ++i)
          {
            result.push_back(from_msgpack_internal(v, idx));
          }
          return result;
        }
        case 0xdd: // array 32
        {
          basic_json result = value_t::array;
          const auto len =
              static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
          idx += 4; // skip 4 size bytes
          for (size_t i = 0; i < len; ++i)
          {
            result.push_back(from_msgpack_internal(v, idx));
          }
          return result;
        }
        case 0xde: // map 16
        {
          basic_json result = value_t::object;
          const auto len =
              static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
          idx += 2; // skip 2 size bytes
          for (size_t i = 0; i < len; ++i)
          {
            std::string key = from_msgpack_internal(v, idx);
            result[key] = from_msgpack_internal(v, idx);
          }
          return result;
        }
        case 0xdf: // map 32
        {
          basic_json result = value_t::object;
          const auto len =
              static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
          idx += 4; // skip 4 size bytes
          for (size_t i = 0; i < len; ++i)
          {
            std::string key = from_msgpack_internal(v, idx);
            result[key] = from_msgpack_internal(v, idx);
          }
          return result;
        }

        default:
        {
          JSON_THROW(std::invalid_argument(
              "error parsing a msgpack @ " + std::to_string(current_idx) +
              ": " + std::to_string(static_cast<int>(v[current_idx]))));
        }
      }
    }
  }

  /*!
  @brief create a JSON value from a given CBOR vector

  @param[in] v  CBOR serialization
  @param[in] idx  byte index to start reading from @a v

  @return deserialized JSON value

  @throw std::invalid_argument if unsupported features from CBOR were used in
  the given vector @a v or if the input is not valid CBOR
  @throw std::out_of_range if the given vector ends prematurely

  @sa https://tools.ietf.org/html/rfc7049
  */
  static basic_json from_cbor_internal(const std::vector<uint8_t> &v,
                                       size_t &idx)
  {
    // store and increment index
    const size_t current_idx = idx++;

    switch (v.at(current_idx))
    {
      // Integer 0x00..0x17 (0..23)
      case 0x00:
      case 0x01:
      case 0x02:
      case 0x03:
      case 0x04:
      case 0x05:
      case 0x06:
      case 0x07:
      case 0x08:
      case 0x09:
      case 0x0a:
      case 0x0b:
      case 0x0c:
      case 0x0d:
      case 0x0e:
      case 0x0f:
      case 0x10:
      case 0x11:
      case 0x12:
      case 0x13:
      case 0x14:
      case 0x15:
      case 0x16:
      case 0x17:
      {
        return v[current_idx];
      }

      case 0x18: // Unsigned integer (one-byte uint8_t follows)
      {
        idx += 1; // skip content byte
        return get_from_vector<uint8_t>(v, current_idx);
      }

      case 0x19: // Unsigned integer (two-byte uint16_t follows)
      {
        idx += 2; // skip 2 content bytes
        return get_from_vector<uint16_t>(v, current_idx);
      }

      case 0x1a: // Unsigned integer (four-byte uint32_t follows)
      {
        idx += 4; // skip 4 content bytes
        return get_from_vector<uint32_t>(v, current_idx);
      }

      case 0x1b: // Unsigned integer (eight-byte uint64_t follows)
      {
        idx += 8; // skip 8 content bytes
        return get_from_vector<uint64_t>(v, current_idx);
      }

      // Negative integer -1-0x00..-1-0x17 (-1..-24)
      case 0x20:
      case 0x21:
      case 0x22:
      case 0x23:
      case 0x24:
      case 0x25:
      case 0x26:
      case 0x27:
      case 0x28:
      case 0x29:
      case 0x2a:
      case 0x2b:
      case 0x2c:
      case 0x2d:
      case 0x2e:
      case 0x2f:
      case 0x30:
      case 0x31:
      case 0x32:
      case 0x33:
      case 0x34:
      case 0x35:
      case 0x36:
      case 0x37:
      {
        return static_cast<int8_t>(0x20 - 1 - v[current_idx]);
      }

      case 0x38: // Negative integer (one-byte uint8_t follows)
      {
        idx += 1; // skip content byte
        // must be uint8_t !
        return static_cast<number_integer_t>(-1) -
               get_from_vector<uint8_t>(v, current_idx);
      }

      case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
      {
        idx += 2; // skip 2 content bytes
        return static_cast<number_integer_t>(-1) -
               get_from_vector<uint16_t>(v, current_idx);
      }

      case 0x3a: // Negative integer -1-n (four-byte uint32_t follows)
      {
        idx += 4; // skip 4 content bytes
        return static_cast<number_integer_t>(-1) -
               get_from_vector<uint32_t>(v, current_idx);
      }

      case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows)
      {
        idx += 8; // skip 8 content bytes
        return static_cast<number_integer_t>(-1) -
               static_cast<number_integer_t>(
                   get_from_vector<uint64_t>(v, current_idx));
      }

      // UTF-8 string (0x00..0x17 bytes follow)
      case 0x60:
      case 0x61:
      case 0x62:
      case 0x63:
      case 0x64:
      case 0x65:
      case 0x66:
      case 0x67:
      case 0x68:
      case 0x69:
      case 0x6a:
      case 0x6b:
      case 0x6c:
      case 0x6d:
      case 0x6e:
      case 0x6f:
      case 0x70:
      case 0x71:
      case 0x72:
      case 0x73:
      case 0x74:
      case 0x75:
      case 0x76:
      case 0x77:
      {
        const auto len = static_cast<size_t>(v[current_idx] - 0x60);
        const size_t offset = current_idx + 1;
        idx += len; // skip content bytes
        check_length(v.size(), len, offset);
        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                           len);
      }

      case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
      {
        const auto len =
            static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
        const size_t offset = current_idx + 2;
        idx += len + 1; // skip size byte + content bytes
        check_length(v.size(), len, offset);
        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                           len);
      }

      case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
      {
        const auto len =
            static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
        const size_t offset = current_idx + 3;
        idx += len + 2; // skip 2 size bytes + content bytes
        check_length(v.size(), len, offset);
        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                           len);
      }

      case 0x7a: // UTF-8 string (four-byte uint32_t for n follow)
      {
        const auto len =
            static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
        const size_t offset = current_idx + 5;
        idx += len + 4; // skip 4 size bytes + content bytes
        check_length(v.size(), len, offset);
        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                           len);
      }

      case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow)
      {
        const auto len =
            static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
        const size_t offset = current_idx + 9;
        idx += len + 8; // skip 8 size bytes + content bytes
        check_length(v.size(), len, offset);
        return std::string(reinterpret_cast<const char *>(v.data()) + offset,
                           len);
      }

      case 0x7f: // UTF-8 string (indefinite length)
      {
        std::string result;
        while (v.at(idx) != 0xff)
        {
          string_t s = from_cbor_internal(v, idx);
          result += s;
        }
        // skip break byte (0xFF)
        idx += 1;
        return result;
      }

      // array (0x00..0x17 data items follow)
      case 0x80:
      case 0x81:
      case 0x82:
      case 0x83:
      case 0x84:
      case 0x85:
      case 0x86:
      case 0x87:
      case 0x88:
      case 0x89:
      case 0x8a:
      case 0x8b:
      case 0x8c:
      case 0x8d:
      case 0x8e:
      case 0x8f:
      case 0x90:
      case 0x91:
      case 0x92:
      case 0x93:
      case 0x94:
      case 0x95:
      case 0x96:
      case 0x97:
      {
        basic_json result = value_t::array;
        const auto len = static_cast<size_t>(v[current_idx] - 0x80);
        for (size_t i = 0; i < len; ++i)
        {
          result.push_back(from_cbor_internal(v, idx));
        }
        return result;
      }
      case 0x98: // array (one-byte uint8_t for n follows)
      {
        basic_json result = value_t::array;
        const auto len =
            static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
        idx += 1; // skip 1 size byte
        for (size_t i = 0; i < len; ++i)
        {
          result.push_back(from_cbor_internal(v, idx));
        }
        return result;
      }
      case 0x99: // array (two-byte uint16_t for n follow)
      {
        basic_json result = value_t::array;
        const auto len =
            static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
        idx += 2; // skip 4 size bytes
        for (size_t i = 0; i < len; ++i)
        {
          result.push_back(from_cbor_internal(v, idx));
        }
        return result;
      }
      case 0x9a: // array (four-byte uint32_t for n follow)
      {
        basic_json result = value_t::array;
        const auto len =
            static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
        idx += 4; // skip 4 size bytes
        for (size_t i = 0; i < len; ++i)
        {
          result.push_back(from_cbor_internal(v, idx));
        }
        return result;
      }
      case 0x9b: // array (eight-byte uint64_t for n follow)
      {
        basic_json result = value_t::array;
        const auto len =
            static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
        idx += 8; // skip 8 size bytes
        for (size_t i = 0; i < len; ++i)
        {
          result.push_back(from_cbor_internal(v, idx));
        }
        return result;
      }
      case 0x9f: // array (indefinite length)
      {
        basic_json result = value_t::array;
        while (v.at(idx) != 0xff)
        {
          result.push_back(from_cbor_internal(v, idx));
        }
        // skip break byte (0xFF)
        idx += 1;
        return result;
      }

      // map (0x00..0x17 pairs of data items follow)
      case 0xa0:
      case 0xa1:
      case 0xa2:
      case 0xa3:
      case 0xa4:
      case 0xa5:
      case 0xa6:
      case 0xa7:
      case 0xa8:
      case 0xa9:
      case 0xaa:
      case 0xab:
      case 0xac:
      case 0xad:
      case 0xae:
      case 0xaf:
      case 0xb0:
      case 0xb1:
      case 0xb2:
      case 0xb3:
      case 0xb4:
      case 0xb5:
      case 0xb6:
      case 0xb7:
      {
        basic_json result = value_t::object;
        const auto len = static_cast<size_t>(v[current_idx] - 0xa0);
        for (size_t i = 0; i < len; ++i)
        {
          std::string key = from_cbor_internal(v, idx);
          result[key] = from_cbor_internal(v, idx);
        }
        return result;
      }
      case 0xb8: // map (one-byte uint8_t for n follows)
      {
        basic_json result = value_t::object;
        const auto len =
            static_cast<size_t>(get_from_vector<uint8_t>(v, current_idx));
        idx += 1; // skip 1 size byte
        for (size_t i = 0; i < len; ++i)
        {
          std::string key = from_cbor_internal(v, idx);
          result[key] = from_cbor_internal(v, idx);
        }
        return result;
      }
      case 0xb9: // map (two-byte uint16_t for n follow)
      {
        basic_json result = value_t::object;
        const auto len =
            static_cast<size_t>(get_from_vector<uint16_t>(v, current_idx));
        idx += 2; // skip 2 size bytes
        for (size_t i = 0; i < len; ++i)
        {
          std::string key = from_cbor_internal(v, idx);
          result[key] = from_cbor_internal(v, idx);
        }
        return result;
      }
      case 0xba: // map (four-byte uint32_t for n follow)
      {
        basic_json result = value_t::object;
        const auto len =
            static_cast<size_t>(get_from_vector<uint32_t>(v, current_idx));
        idx += 4; // skip 4 size bytes
        for (size_t i = 0; i < len; ++i)
        {
          std::string key = from_cbor_internal(v, idx);
          result[key] = from_cbor_internal(v, idx);
        }
        return result;
      }
      case 0xbb: // map (eight-byte uint64_t for n follow)
      {
        basic_json result = value_t::object;
        const auto len =
            static_cast<size_t>(get_from_vector<uint64_t>(v, current_idx));
        idx += 8; // skip 8 size bytes
        for (size_t i = 0; i < len; ++i)
        {
          std::string key = from_cbor_internal(v, idx);
          result[key] = from_cbor_internal(v, idx);
        }
        return result;
      }
      case 0xbf: // map (indefinite length)
      {
        basic_json result = value_t::object;
        while (v.at(idx) != 0xff)
        {
          std::string key = from_cbor_internal(v, idx);
          result[key] = from_cbor_internal(v, idx);
        }
        // skip break byte (0xFF)
        idx += 1;
        return result;
      }
      case 0xf4: // false
      {
        return false;
      }

      case 0xf5: // true
      {
        return true;
      }

      case 0xf6: // null
      {
        return value_t::null;
      }

      case 0xf9: // Half-Precision Float (two-byte IEEE 754)
      {
        idx += 2; // skip two content bytes

        // code from RFC 7049, Appendix D, Figure 3:
        // As half-precision floating-point numbers were only added to
        // IEEE 754 in 2008, today's programming platforms often still
        // only have limited support for them. It is very easy to
        // include at least decoding support for them even without such
        // support. An example of a small decoder for half-precision
        // floating-point numbers in the C language is shown in Fig. 3.
        const int half = (v.at(current_idx + 1) << 8) + v.at(current_idx + 2);
        const int exp = (half >> 10) & 0x1f;
        const int mant = half & 0x3ff;
        double val;
        if (exp == 0)
        {
          val = std::ldexp(mant, -24);
        }
        else if (exp != 31)
        {
          val = std::ldexp(mant + 1024, exp - 25);
        }
        else
        {
          val = mant == 0 ? INFINITY : NAN;
        }
        return (half & 0x8000) != 0 ? -val : val;
      }

      case 0xfa: // Single-Precision Float (four-byte IEEE 754)
      {
        // copy bytes in reverse order into the float variable
        float res;
        for (size_t byte = 0; byte < sizeof(float); ++byte)
        {
          reinterpret_cast<uint8_t *>(&res)[sizeof(float) - byte - 1] =
              v.at(current_idx + 1 + byte);
        }
        idx += sizeof(float); // skip content bytes
        return res;
      }

      case 0xfb: // Double-Precision Float (eight-byte IEEE 754)
      {
        // copy bytes in reverse order into the double variable
        double res;
        for (size_t byte = 0; byte < sizeof(double); ++byte)
        {
          reinterpret_cast<uint8_t *>(&res)[sizeof(double) - byte - 1] =
              v.at(current_idx + 1 + byte);
        }
        idx += sizeof(double); // skip content bytes
        return res;
      }

      default: // anything else (0xFF is handled inside the other types)
      {
        JSON_THROW(std::invalid_argument(
            "error parsing a CBOR @ " + std::to_string(current_idx) + ": " +
            std::to_string(static_cast<int>(v[current_idx]))));
      }
public:
  /*!
  @brief create a MessagePack serialization of a given JSON value
  Serializes a given JSON value @a j to a byte vector using the MessagePack
  serialization format. MessagePack is a binary serialization format which
  aims to be more compact than JSON itself, yet more efficient to parse.

  @param[in] j  JSON value to serialize
  @return MessagePack serialization as byte vector

  @complexity Linear in the size of the JSON value @a j.

  @liveexample{The example shows the serialization of a JSON value to a byte
  vector in MessagePack format.,to_msgpack}

  @sa http://msgpack.org
  @sa @ref from_msgpack(const std::vector<uint8_t>&) for the analogous
      deserialization
  @sa @ref to_cbor(const basic_json& for the related CBOR format
  */
  static std::vector<uint8_t> to_msgpack(const basic_json &j)
  {
    std::vector<uint8_t> result;
    to_msgpack_internal(j, result);
    return result;
  }
  /*!
  @brief create a JSON value from a byte vector in MessagePack format
  Deserializes a given byte vector @a v to a JSON value using the MessagePack
  serialization format.
  @param[in] v  a byte vector in MessagePack format
  @return deserialized JSON value
  @throw std::invalid_argument if unsupported features from MessagePack were
  used in the given vector @a v or if the input is not valid MessagePack
  @throw std::out_of_range if the given vector ends prematurely

  @complexity Linear in the size of the byte vector @a v.

  @liveexample{The example shows the deserialization of a byte vector in
  MessagePack format to a JSON value.,from_msgpack}

  @sa http://msgpack.org
  @sa @ref to_msgpack(const basic_json&) for the analogous serialization
  @sa @ref from_cbor(const std::vector<uint8_t>&) for the related CBOR format
  */
  static basic_json from_msgpack(const std::vector<uint8_t> &v)
  {
    size_t i = 0;
    return from_msgpack_internal(v, i);
  }

  /*!
  @brief create a MessagePack serialization of a given JSON value

  Serializes a given JSON value @a j to a byte vector using the CBOR (Concise
  Binary Object Representation) serialization format. CBOR is a binary
  serialization format which aims to be more compact than JSON itself, yet
  more efficient to parse.

  @param[in] j  JSON value to serialize
  @return MessagePack serialization as byte vector

  @complexity Linear in the size of the JSON value @a j.

  @liveexample{The example shows the serialization of a JSON value to a byte
  vector in CBOR format.,to_cbor}

  @sa http://cbor.io
  @sa @ref from_cbor(const std::vector<uint8_t>&) for the analogous
      deserialization
  @sa @ref to_msgpack(const basic_json& for the related MessagePack format
  */
  static std::vector<uint8_t> to_cbor(const basic_json &j)
  {
    std::vector<uint8_t> result;
    to_cbor_internal(j, result);
    return result;
  }

  /*!
  @brief create a JSON value from a byte vector in CBOR format

  Deserializes a given byte vector @a v to a JSON value using the CBOR
  (Concise Binary Object Representation) serialization format.

  @param[in] v  a byte vector in CBOR format
  @return deserialized JSON value

  @throw std::invalid_argument if unsupported features from CBOR were used in
  the given vector @a v or if the input is not valid MessagePack
  @throw std::out_of_range if the given vector ends prematurely

  @complexity Linear in the size of the byte vector @a v.

  @liveexample{The example shows the deserialization of a byte vector in CBOR
  format to a JSON value.,from_cbor}

  @sa http://cbor.io
  @sa @ref to_cbor(const basic_json&) for the analogous serialization
  @sa @ref from_msgpack(const std::vector<uint8_t>&) for the related
      MessagePack format
  */
  static basic_json from_cbor(const std::vector<uint8_t> &v)
  {
    size_t i = 0;
    return from_cbor_internal(v, i);
  }

  /// @}

private:
  ///////////////////////////
  // convenience functions //
  ///////////////////////////

  /*!
  @brief return the type as string

  Returns the type name as string to be used in error messages - usually to
  indicate that a function was called on a wrong JSON type.

  @return basically a string representation of a the @a m_type member

  @complexity Constant.

  @since version 1.0.0
  */
  std::string type_name() const
  {
    switch (m_type)
    {
      case value_t::null:
        return "null";
      case value_t::object:
        return "object";
      case value_t::array:
        return "array";
      case value_t::string:
        return "string";
      case value_t::boolean:
        return "boolean";
      case value_t::discarded:
        return "discarded";
      default:
        return "number";
    }
  }

  /*!
  @brief calculates the extra space to escape a JSON string

  @param[in] s  the string to escape
  @return the number of characters required to escape string @a s

  @complexity Linear in the length of string @a s.
  */
  static std::size_t extra_space(const string_t &s) noexcept
  {
    return std::accumulate(s.begin(), s.end(), size_t{},
                           [](size_t res, typename string_t::value_type c) {
                             switch (c)
                             {
                               case '"':
                               case '\\':
                               case '\b':
                               case '\f':
                               case '\n':
                               case '\r':
                               case '\t':
                               {
                                 // from c (1 byte) to \x (2 bytes)
                                 return res + 1;
                               }
                               default:
                               {
                                 if (c >= 0x00 and c <= 0x1f)
                                 {
                                   // from c (1 byte) to \uxxxx (6 bytes)
                                   return res + 5;
                                 }
  /*!
  @brief escape a string
  Escape a string by replacing certain special characters by a sequence of
  an escape character (backslash) and another character and other control
  characters by a sequence of "\u" followed by a four-digit hex
  representation.
  @param[in] s  the string to escape
  @return  the escaped string
  @complexity Linear in the length of string @a s.
  */
  static string_t escape_string(const string_t &s)
  {
    const auto space = extra_space(s);
    if (space == 0)
    {
      return s;
    }
    // create a result string of necessary size
    string_t result(s.size() + space, '\\');
    std::size_t pos = 0;

    for (const auto &c : s)
      switch (c)
      {
        // quotation mark (0x22)
        case '"':
          result[pos + 1] = '"';
          pos += 2;
          break;
        }
        // reverse solidus (0x5c)
        case '\\':
        {
          // nothing to change
          pos += 2;
          break;
        }
        // backspace (0x08)
        case '\b':
        {
          result[pos + 1] = 'b';
          pos += 2;
          break;
        // formfeed (0x0c)
        case '\f':
          result[pos + 1] = 'f';
          pos += 2;